From e63291850fd0795c5700e25e67e5dee89ba54c5f Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Tue, 1 Dec 2015 05:49:27 -0800 Subject: onos commit hash c2999f30c69e50df905a9d175ef80b3f23a98514 Change-Id: I2bb8562c4942b6d6a6d60b663db2e17540477b81 Signed-off-by: Ashlee Young --- framework/src/onos/apps/aaa/features.xml | 1 - .../main/java/org/onosproject/aaa/AaaConfig.java | 239 +++++++++ .../main/java/org/onosproject/aaa/AaaManager.java | 562 +++++++++++++++++++++ .../org/onosproject/aaa/AaaIntegrationTest.java | 151 ++++++ .../java/org/onosproject/aaa/AaaManagerTest.java | 258 ++++++++++ .../test/java/org/onosproject/aaa/AaaTestBase.java | 224 ++++++++ 6 files changed, 1434 insertions(+), 1 deletion(-) create mode 100644 framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaConfig.java create mode 100644 framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaManager.java create mode 100644 framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaIntegrationTest.java create mode 100644 framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaManagerTest.java create mode 100644 framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaTestBase.java (limited to 'framework/src/onos/apps/aaa') diff --git a/framework/src/onos/apps/aaa/features.xml b/framework/src/onos/apps/aaa/features.xml index 3825ec5c..e965d41a 100644 --- a/framework/src/onos/apps/aaa/features.xml +++ b/framework/src/onos/apps/aaa/features.xml @@ -15,7 +15,6 @@ ~ limitations under the License. --> - mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features onos-api diff --git a/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaConfig.java b/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaConfig.java new file mode 100644 index 00000000..db821ca2 --- /dev/null +++ b/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaConfig.java @@ -0,0 +1,239 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.onosproject.aaa; + +import org.onosproject.core.ApplicationId; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.basics.BasicElementConfig; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Network config for the AAA app. + */ +public class AaaConfig extends Config { + + private static final String RADIUS_IP = "radiusIp"; + private static final String RADIUS_SERVER_PORT = "1812"; + private static final String RADIUS_MAC = "radiusMac"; + private static final String NAS_IP = "nasIp"; + private static final String NAS_MAC = "nasMac"; + private static final String RADIUS_SECRET = "radiusSecret"; + private static final String RADIUS_SWITCH = "radiusSwitch"; + private static final String RADIUS_PORT = "radiusPort"; + + // RADIUS server IP address + protected static final String DEFAULT_RADIUS_IP = "10.128.10.4"; + + // RADIUS MAC address + protected static final String DEFAULT_RADIUS_MAC = "00:00:00:00:01:10"; + + // NAS IP address + protected static final String DEFAULT_NAS_IP = "10.128.9.244"; + + // NAS MAC address + protected static final String DEFAULT_NAS_MAC = "00:00:00:00:10:01"; + + // RADIUS server shared secret + protected static final String DEFAULT_RADIUS_SECRET = "ONOSecret"; + + // Radius Switch Id + protected static final String DEFAULT_RADIUS_SWITCH = "of:90e2ba82f97791e9"; + + // Radius Port Number + protected static final String DEFAULT_RADIUS_PORT = "129"; + + // Radius Server UDP Port Number + protected static final String DEFAULT_RADIUS_SERVER_PORT = "1812"; + + /** + * Gets the value of a string property, protecting for an empty + * JSON object. + * + * @param name name of the property + * @param defaultValue default value if none has been specified + * @return String value if one os found, default value otherwise + */ + private String getStringProperty(String name, String defaultValue) { + if (object == null) { + return defaultValue; + } + return get(name, defaultValue); + } + + /** + * Returns the NAS ip. + * + * @return ip address or null if not set + */ + public InetAddress nasIp() { + try { + return InetAddress.getByName(getStringProperty(NAS_IP, DEFAULT_NAS_IP)); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * Sets the NAS ip. + * + * @param ip new ip address; null to clear + * @return self + */ + public BasicElementConfig nasIp(String ip) { + return (BasicElementConfig) setOrClear(NAS_IP, ip); + } + + /** + * Returns the RADIUS server ip. + * + * @return ip address or null if not set + */ + public InetAddress radiusIp() { + try { + return InetAddress.getByName(getStringProperty(RADIUS_IP, DEFAULT_RADIUS_IP)); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * Sets the RADIUS server ip. + * + * @param ip new ip address; null to clear + * @return self + */ + public BasicElementConfig radiusIp(String ip) { + return (BasicElementConfig) setOrClear(RADIUS_IP, ip); + } + + /** + * Returns the RADIUS MAC address. + * + * @return mac address or null if not set + */ + public String radiusMac() { + return getStringProperty(RADIUS_MAC, DEFAULT_RADIUS_MAC); + } + + /** + * Sets the RADIUS MAC address. + * + * @param mac new MAC address; null to clear + * @return self + */ + public BasicElementConfig radiusMac(String mac) { + return (BasicElementConfig) setOrClear(RADIUS_MAC, mac); + } + + /** + * Returns the RADIUS MAC address. + * + * @return mac address or null if not set + */ + public String nasMac() { + return getStringProperty(NAS_MAC, DEFAULT_NAS_MAC); + } + + /** + * Sets the RADIUS MAC address. + * + * @param mac new MAC address; null to clear + * @return self + */ + public BasicElementConfig nasMac(String mac) { + return (BasicElementConfig) setOrClear(NAS_MAC, mac); + } + + /** + * Returns the RADIUS secret. + * + * @return radius secret or null if not set + */ + public String radiusSecret() { + return getStringProperty(RADIUS_SECRET, DEFAULT_RADIUS_SECRET); + } + + /** + * Sets the RADIUS secret. + * + * @param secret new MAC address; null to clear + * @return self + */ + public BasicElementConfig radiusSecret(String secret) { + return (BasicElementConfig) setOrClear(RADIUS_SECRET, secret); + } + + /** + * Returns the ID of the RADIUS switch. + * + * @return radius switch ID or null if not set + */ + public String radiusSwitch() { + return getStringProperty(RADIUS_SWITCH, DEFAULT_RADIUS_SWITCH); + } + + /** + * Sets the ID of the RADIUS switch. + * + * @param switchId new RADIUS switch ID; null to clear + * @return self + */ + public BasicElementConfig radiusSwitch(String switchId) { + return (BasicElementConfig) setOrClear(RADIUS_SWITCH, switchId); + } + + /** + * Returns the RADIUS port. + * + * @return radius port or null if not set + */ + public long radiusPort() { + return Integer.parseInt(getStringProperty(RADIUS_PORT, DEFAULT_RADIUS_PORT)); + } + + /** + * Sets the RADIUS port. + * + * @param port new RADIUS port; null to clear + * @return self + */ + public BasicElementConfig radiusPort(long port) { + return (BasicElementConfig) setOrClear(RADIUS_PORT, port); + } + + /** + * Returns the RADIUS server UDP port. + * + * @return radius server UDP port. + */ + public short radiusServerUdpPort() { + return Short.parseShort(getStringProperty(RADIUS_SERVER_PORT, + DEFAULT_RADIUS_SERVER_PORT)); + } + + /** + * Sets the RADIUS port. + * + * @param port new RADIUS UDP port; -1 to clear + * @return self + */ + public BasicElementConfig radiusServerUdpPort(short port) { + return (BasicElementConfig) setOrClear(RADIUS_SERVER_PORT, (long) port); + } + +} diff --git a/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaManager.java b/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaManager.java new file mode 100644 index 00000000..dd324eee --- /dev/null +++ b/framework/src/onos/apps/aaa/src/main/java/org/onosproject/aaa/AaaManager.java @@ -0,0 +1,562 @@ +/* + * Copyright 2015 AT&T Foundry + * + * 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.onosproject.aaa; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.EAP; +import org.onlab.packet.EAPOL; +import org.onlab.packet.EthType; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.RADIUS; +import org.onlab.packet.RADIUSAttribute; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigRegistry; +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.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.xosintegration.VoltTenantService; +import org.slf4j.Logger; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY; +import static org.onosproject.net.packet.PacketPriority.CONTROL; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * AAA application for ONOS. + */ +@Component(immediate = true) +public class AaaManager { + + // for verbose output + private final Logger log = getLogger(getClass()); + + // a list of our dependencies : + // to register with ONOS as an application - described next + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + // to receive Packet-in events that we'll respond to + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected VoltTenantService voltTenantService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry netCfgService; + + // Parsed RADIUS server addresses + protected InetAddress radiusIpAddress; + protected String radiusMacAddress; + + // NAS IP address + protected InetAddress nasIpAddress; + protected String nasMacAddress; + + // RADIUS server secret + protected String radiusSecret; + + // ID of RADIUS switch + protected String radiusSwitch; + + // RADIUS port number + protected long radiusPort; + + // RADIUS server TCP port number + protected short radiusServerPort; + + // our application-specific event handler + private ReactivePacketProcessor processor = new ReactivePacketProcessor(); + + // our unique identifier + private ApplicationId appId; + + // Socket used for UDP communications with RADIUS server + private DatagramSocket radiusSocket; + + // Executor for RADIUS communication thread + private ExecutorService executor; + + // Configuration properties factory + private final ConfigFactory factory = + new ConfigFactory(APP_SUBJECT_FACTORY, + AaaConfig.class, + "AAA") { + @Override + public AaaConfig createConfig() { + return new AaaConfig(); + } + }; + + // Listener for config changes + private final InternalConfigListener cfgListener = new InternalConfigListener(); + + /** + * Builds an EAPOL packet based on the given parameters. + * + * @param dstMac destination MAC address + * @param srcMac source MAC address + * @param vlan vlan identifier + * @param eapolType EAPOL type + * @param eap EAP payload + * @return Ethernet frame + */ + private static Ethernet buildEapolResponse(MacAddress dstMac, MacAddress srcMac, + short vlan, byte eapolType, EAP eap) { + + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(dstMac.toBytes()); + eth.setSourceMACAddress(srcMac.toBytes()); + eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort()); + if (vlan != Ethernet.VLAN_UNTAGGED) { + eth.setVlanID(vlan); + } + //eapol header + EAPOL eapol = new EAPOL(); + eapol.setEapolType(eapolType); + eapol.setPacketLength(eap.getLength()); + + //eap part + eapol.setPayload(eap); + + eth.setPayload(eapol); + eth.setPad(true); + return eth; + } + + @Activate + public void activate() { + netCfgService.addListener(cfgListener); + netCfgService.registerConfigFactory(factory); + + // "org.onosproject.aaa" is the FQDN of our app + appId = coreService.registerApplication("org.onosproject.aaa"); + + cfgListener.reconfigureNetwork(netCfgService.getConfig(appId, AaaConfig.class)); + + // register our event handler + packetService.addProcessor(processor, PacketProcessor.director(2)); + requestIntercepts(); + + StateMachine.initializeMaps(); + + try { + radiusSocket = new DatagramSocket(radiusServerPort); + } catch (Exception ex) { + log.error("Can't open RADIUS socket", ex); + } + + executor = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setNameFormat("AAA-radius-%d").build()); + executor.execute(radiusListener); + } + + @Deactivate + public void deactivate() { + appId = coreService.registerApplication("org.onosproject.aaa"); + withdrawIntercepts(); + // de-register and null our handler + packetService.removeProcessor(processor); + processor = null; + StateMachine.destroyMaps(); + radiusSocket.close(); + executor.shutdownNow(); + } + + protected void sendRadiusPacket(RADIUS radiusPacket) { + + try { + final byte[] data = radiusPacket.serialize(); + final DatagramSocket socket = radiusSocket; + + DatagramPacket packet = + new DatagramPacket(data, data.length, + radiusIpAddress, radiusServerPort); + + socket.send(packet); + } catch (IOException e) { + log.info("Cannot send packet to RADIUS server", e); + } + } + + /** + * Request packet in via PacketService. + */ + private void requestIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort()); + packetService.requestPackets(selector.build(), + CONTROL, appId); + } + + /** + * Cancel request for packet in via PacketService. + */ + private void withdrawIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort()); + packetService.cancelPackets(selector.build(), CONTROL, appId); + } + + /** + * Send the ethernet packet to the supplicant. + * + * @param ethernetPkt the ethernet packet + * @param connectPoint the connect point to send out + */ + private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) { + TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build(); + OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(), + treatment, ByteBuffer.wrap(ethernetPkt.serialize())); + packetService.emit(packet); + } + + // our handler defined as a private inner class + + /** + * Packet processor responsible for forwarding packets along their paths. + */ + private class ReactivePacketProcessor implements PacketProcessor { + @Override + public void process(PacketContext context) { + + // Extract the original Ethernet frame from the packet information + InboundPacket pkt = context.inPacket(); + Ethernet ethPkt = pkt.parsed(); + if (ethPkt == null) { + return; + } + try { + // identify if incoming packet comes from supplicant (EAP) or RADIUS + switch (EthType.EtherType.lookup(ethPkt.getEtherType())) { + case EAPOL: + handleSupplicantPacket(context.inPacket()); + break; + default: + log.trace("Skipping Ethernet packet type {}", + EthType.EtherType.lookup(ethPkt.getEtherType())); + } + } catch (StateMachineException e) { + log.warn("Unable to process RADIUS packet:", e); + } + } + + /** + * Creates and initializes common fields of a RADIUS packet. + * + * @param stateMachine state machine for the request + * @param eapPacket EAP packet + * @return RADIUS packet + */ + private RADIUS getRadiusPayload(StateMachine stateMachine, byte identifier, EAP eapPacket) { + RADIUS radiusPayload = + new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST, + eapPacket.getIdentifier()); + + // set Request Authenticator in StateMachine + stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode()); + + radiusPayload.setIdentifier(identifier); + radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME, + stateMachine.username()); + + radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP, + AaaManager.this.nasIpAddress.getAddress()); + + radiusPayload.encapsulateMessage(eapPacket); + + return radiusPayload; + } + + /** + * Handles PAE packets (supplicant). + * + * @param inPacket Ethernet packet coming from the supplicant + */ + private void handleSupplicantPacket(InboundPacket inPacket) throws StateMachineException { + Ethernet ethPkt = inPacket.parsed(); + // Where does it come from? + MacAddress srcMac = ethPkt.getSourceMAC(); + + DeviceId deviceId = inPacket.receivedFrom().deviceId(); + PortNumber portNumber = inPacket.receivedFrom().port(); + String sessionId = deviceId.toString() + portNumber.toString(); + StateMachine stateMachine = StateMachine.lookupStateMachineBySessionId(sessionId); + if (stateMachine == null) { + stateMachine = new StateMachine(sessionId, voltTenantService); + } + + + EAPOL eapol = (EAPOL) ethPkt.getPayload(); + + switch (eapol.getEapolType()) { + case EAPOL.EAPOL_START: + stateMachine.start(); + stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom()); + + //send an EAP Request/Identify to the supplicant + EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null); + Ethernet eth = buildEapolResponse(srcMac, MacAddress.valueOf(nasMacAddress), + ethPkt.getVlanID(), EAPOL.EAPOL_PACKET, + eapPayload); + stateMachine.setSupplicantAddress(srcMac); + stateMachine.setVlanId(ethPkt.getVlanID()); + + sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint()); + + break; + case EAPOL.EAPOL_PACKET: + RADIUS radiusPayload; + // check if this is a Response/Identify or a Response/TLS + EAP eapPacket = (EAP) eapol.getPayload(); + + byte dataType = eapPacket.getDataType(); + switch (dataType) { + + case EAP.ATTR_IDENTITY: + // request id access to RADIUS + stateMachine.setUsername(eapPacket.getData()); + + radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket); + radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret); + + sendRadiusPacket(radiusPayload); + + // change the state to "PENDING" + stateMachine.requestAccess(); + break; + case EAP.ATTR_MD5: + // verify if the EAP identifier corresponds to the + // challenge identifier from the client state + // machine. + if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) { + //send the RADIUS challenge response + radiusPayload = + getRadiusPayload(stateMachine, + stateMachine.identifier(), + eapPacket); + + radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE, + stateMachine.challengeState()); + radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret); + sendRadiusPacket(radiusPayload); + } + break; + case EAP.ATTR_TLS: + // request id access to RADIUS + radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket); + + radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE, + stateMachine.challengeState()); + stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode()); + + radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret); + sendRadiusPacket(radiusPayload); + + if (stateMachine.state() != StateMachine.STATE_PENDING) { + stateMachine.requestAccess(); + } + + break; + default: + return; + } + break; + default: + log.trace("Skipping EAPOL message {}", eapol.getEapolType()); + } + + } + } + + class RadiusListener implements Runnable { + + /** + * Handles RADIUS packets. + * + * @param radiusPacket RADIUS packet coming from the RADIUS server. + * @throws StateMachineException if an illegal state transition is triggered + */ + protected void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException { + StateMachine stateMachine = StateMachine.lookupStateMachineById(radiusPacket.getIdentifier()); + if (stateMachine == null) { + log.error("Invalid session identifier, exiting..."); + return; + } + + EAP eapPayload; + Ethernet eth; + switch (radiusPacket.getCode()) { + case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE: + byte[] challengeState = + radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue(); + eapPayload = radiusPacket.decapsulateMessage(); + stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState); + eth = buildEapolResponse(stateMachine.supplicantAddress(), + MacAddress.valueOf(nasMacAddress), + stateMachine.vlanId(), + EAPOL.EAPOL_PACKET, + eapPayload); + sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint()); + break; + case RADIUS.RADIUS_CODE_ACCESS_ACCEPT: + //send an EAPOL - Success to the supplicant. + byte[] eapMessage = + radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue(); + eapPayload = new EAP(); + eapPayload = (EAP) eapPayload.deserialize(eapMessage, 0, eapMessage.length); + eth = buildEapolResponse(stateMachine.supplicantAddress(), + MacAddress.valueOf(nasMacAddress), + stateMachine.vlanId(), + EAPOL.EAPOL_PACKET, + eapPayload); + sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint()); + + stateMachine.authorizeAccess(); + break; + case RADIUS.RADIUS_CODE_ACCESS_REJECT: + stateMachine.denyAccess(); + break; + default: + log.warn("Unknown RADIUS message received with code: {}", radiusPacket.getCode()); + } + } + + + @Override + public void run() { + boolean done = false; + int packetNumber = 1; + + log.info("UDP listener thread starting up"); + RADIUS inboundRadiusPacket; + while (!done) { + try { + byte[] packetBuffer = new byte[RADIUS.RADIUS_MAX_LENGTH]; + DatagramPacket inboundBasePacket = + new DatagramPacket(packetBuffer, packetBuffer.length); + DatagramSocket socket = radiusSocket; + socket.receive(inboundBasePacket); + log.info("Packet #{} received", packetNumber++); + try { + inboundRadiusPacket = + RADIUS.deserializer() + .deserialize(inboundBasePacket.getData(), + 0, + inboundBasePacket.getLength()); + handleRadiusPacket(inboundRadiusPacket); + } catch (DeserializationException dex) { + log.error("Cannot deserialize packet", dex); + } catch (StateMachineException sme) { + log.error("Illegal state machine operation", sme); + } + + } catch (IOException e) { + log.info("Socket was closed, exiting listener thread"); + done = true; + } + } + } + } + + RadiusListener radiusListener = new RadiusListener(); + + private class InternalConfigListener implements NetworkConfigListener { + + /** + * Reconfigures the DHCP Server according to the configuration parameters passed. + * + * @param cfg configuration object + */ + private void reconfigureNetwork(AaaConfig cfg) { + AaaConfig newCfg; + if (cfg == null) { + newCfg = new AaaConfig(); + } else { + newCfg = cfg; + } + 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(); + } + if (newCfg.radiusSecret() != null) { + radiusSecret = newCfg.radiusSecret(); + } + if (newCfg.radiusSwitch() != null) { + radiusSwitch = newCfg.radiusSwitch(); + } + if (newCfg.radiusPort() != -1) { + radiusPort = newCfg.radiusPort(); + } + if (newCfg.radiusServerUdpPort() != -1) { + radiusServerPort = newCfg.radiusServerUdpPort(); + } + } + + @Override + public void event(NetworkConfigEvent event) { + + if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED || + event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) && + event.configClass().equals(AaaConfig.class)) { + + AaaConfig cfg = netCfgService.getConfig(appId, AaaConfig.class); + reconfigureNetwork(cfg); + log.info("Reconfigured"); + } + } + } + + +} diff --git a/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaIntegrationTest.java b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaIntegrationTest.java new file mode 100644 index 00000000..6d708fef --- /dev/null +++ b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaIntegrationTest.java @@ -0,0 +1,151 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * 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.onosproject.aaa; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.onlab.packet.EAP; +import org.onlab.packet.EAPOL; +import org.onlab.packet.Ethernet; +import org.onosproject.core.CoreServiceAdapter; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.NetworkConfigRegistryAdapter; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +/** + * Set of tests of the ONOS application component. These use an existing RADIUS + * server and sends live packets over the network to it. + */ +@Ignore ("This should not be run as part of the standard build") +public class AaaIntegrationTest extends AaaTestBase { + + private AaaManager aaa; + + /** + * Mocks the network config registry. + */ + @SuppressWarnings("unchecked") + static final class TestNetworkConfigRegistry + extends NetworkConfigRegistryAdapter { + @Override + public > C getConfig(S subject, Class configClass) { + return (C) new AaaConfig(); + } + } + + /** + * Sets up the services required by the AAA application. + */ + @Before + public void setUp() { + aaa = new AaaManager(); + aaa.netCfgService = new TestNetworkConfigRegistry(); + aaa.coreService = new CoreServiceAdapter(); + aaa.packetService = new MockPacketService(); + aaa.activate(); + } + + /** + * Fetches the sent packet at the given index. The requested packet + * must be the last packet on the list. + * + * @param index index into sent packets array + * @return packet + */ + private Ethernet fetchPacket(int index) { + for (int iteration = 0; iteration < 20; iteration++) { + if (savedPackets.size() > index) { + return (Ethernet) savedPackets.get(index); + } else { + try { + Thread.sleep(250); + } catch (Exception ex) { + return null; + } + } + } + return null; + } + + /** + * Tests the authentication path through the AAA application by sending + * packets to the RADIUS server and checking the state machine + * transitions. + * + * @throws Exception when an unhandled error occurs + */ + @Test + public void testAuthentication() throws Exception { + + // (1) Supplicant start up + + Ethernet startPacket = constructSupplicantStartPacket(); + sendPacket(startPacket); + + Ethernet responsePacket = fetchPacket(0); + assertThat(responsePacket, notNullValue()); + checkRadiusPacket(aaa, responsePacket, EAP.REQUEST); + + // (2) Supplicant identify + + Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null); + sendPacket(identifyPacket); + + // State machine should have been created by now + + StateMachine stateMachine = + StateMachine.lookupStateMachineBySessionId(SESSION_ID); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING)); + + // (3) RADIUS MD5 challenge + + Ethernet radiusChallengeMD5Packet = fetchPacket(1); + assertThat(radiusChallengeMD5Packet, notNullValue()); + checkRadiusPacket(aaa, radiusChallengeMD5Packet, EAP.REQUEST); + + + // (4) Supplicant MD5 response + + Ethernet md5RadiusPacket = + constructSupplicantIdentifyPacket(stateMachine, + EAP.ATTR_MD5, + stateMachine.challengeIdentifier(), + radiusChallengeMD5Packet); + sendPacket(md5RadiusPacket); + + + // (5) RADIUS Success + + Ethernet successRadiusPacket = fetchPacket(2); + assertThat(successRadiusPacket, notNullValue()); + EAPOL successEapol = (EAPOL) successRadiusPacket.getPayload(); + EAP successEap = (EAP) successEapol.getPayload(); + assertThat(successEap.getCode(), is(EAP.SUCCESS)); + + // State machine should be in authorized state + + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED)); + + } + +} + diff --git a/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaManagerTest.java b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaManagerTest.java new file mode 100644 index 00000000..e3bcd9e4 --- /dev/null +++ b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaManagerTest.java @@ -0,0 +1,258 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * 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.onosproject.aaa; + +import com.google.common.base.Charsets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.BasePacket; +import org.onlab.packet.DeserializationException; +import org.onlab.packet.EAP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.RADIUS; +import org.onlab.packet.RADIUSAttribute; +import org.onosproject.core.CoreServiceAdapter; +import org.onosproject.net.config.Config; +import org.onosproject.net.config.NetworkConfigRegistryAdapter; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +/** + * Set of tests of the ONOS application component. + */ +public class AaaManagerTest extends AaaTestBase { + + static final String BAD_IP_ADDRESS = "198.51.100.0"; + + private AaaManager aaaManager; + + class AaaManagerWithoutRadiusServer extends AaaManager { + protected void sendRadiusPacket(RADIUS radiusPacket) { + savePacket(radiusPacket); + } + } + + /** + * Mocks the AAAConfig class to force usage of an unroutable address for the + * RADIUS server. + */ + static class MockAaaConfig extends AaaConfig { + @Override + public InetAddress radiusIp() { + try { + return InetAddress.getByName(BAD_IP_ADDRESS); + } catch (UnknownHostException ex) { + // can't happen + throw new IllegalStateException(ex); + } + } + } + + /** + * Mocks the network config registry. + */ + @SuppressWarnings("unchecked") + private static final class TestNetworkConfigRegistry + extends NetworkConfigRegistryAdapter { + @Override + public > C getConfig(S subject, Class configClass) { + AaaConfig aaaConfig = new MockAaaConfig(); + return (C) aaaConfig; + } + } + + /** + * Constructs an Ethernet packet containing a RADIUS challenge + * packet. + * + * @param challengeCode code to use in challenge packet + * @param challengeType type to use in challenge packet + * @return Ethernet packet + */ + private RADIUS constructRadiusCodeAccessChallengePacket(byte challengeCode, byte challengeType) { + + String challenge = "12345678901234567"; + + EAP eap = new EAP(challengeType, (byte) 1, challengeType, + challenge.getBytes(Charsets.US_ASCII)); + eap.setIdentifier((byte) 1); + + RADIUS radius = new RADIUS(); + radius.setCode(challengeCode); + + radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE, + challenge.getBytes(Charsets.US_ASCII)); + + radius.setPayload(eap); + radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE, + eap.serialize()); + + return radius; + } + + /** + * Sets up the services required by the AAA application. + */ + @Before + public void setUp() { + aaaManager = new AaaManagerWithoutRadiusServer(); + aaaManager.netCfgService = new TestNetworkConfigRegistry(); + aaaManager.coreService = new CoreServiceAdapter(); + aaaManager.packetService = new MockPacketService(); + aaaManager.activate(); + } + + /** + * Tears down the AAA application. + */ + @After + public void tearDown() { + aaaManager.deactivate(); + } + + /** + * Extracts the RADIUS packet from a packet sent by the supplicant. + * + * @param radius RADIUS packet sent by the supplicant + * @throws DeserializationException if deserialization of the packet contents + * fails. + */ + private void checkRadiusPacketFromSupplicant(RADIUS radius) + throws DeserializationException { + assertThat(radius, notNullValue()); + + EAP eap = radius.decapsulateMessage(); + assertThat(eap, notNullValue()); + } + + /** + * Fetches the sent packet at the given index. The requested packet + * must be the last packet on the list. + * + * @param index index into sent packets array + * @return packet + */ + private BasePacket fetchPacket(int index) { + BasePacket packet = savedPackets.get(index); + assertThat(packet, notNullValue()); + return packet; + } + + /** + * Tests the authentication path through the AAA application. + * + * @throws DeserializationException if packed deserialization fails. + */ + @Test + public void testAuthentication() throws Exception { + + // (1) Supplicant start up + + Ethernet startPacket = constructSupplicantStartPacket(); + sendPacket(startPacket); + + Ethernet responsePacket = (Ethernet) fetchPacket(0); + checkRadiusPacket(aaaManager, responsePacket, EAP.ATTR_IDENTITY); + + // (2) Supplicant identify + + Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null); + sendPacket(identifyPacket); + + RADIUS radiusIdentifyPacket = (RADIUS) fetchPacket(1); + + checkRadiusPacketFromSupplicant(radiusIdentifyPacket); + + assertThat(radiusIdentifyPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST)); + assertThat(new String(radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()), + is("testuser")); + + IpAddress nasIp = + IpAddress.valueOf(IpAddress.Version.INET, + radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP) + .getValue()); + assertThat(nasIp.toString(), is(aaaManager.nasIpAddress.getHostAddress())); + + // State machine should have been created by now + + StateMachine stateMachine = + StateMachine.lookupStateMachineBySessionId(SESSION_ID); + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING)); + + // (3) RADIUS MD5 challenge + + RADIUS radiusCodeAccessChallengePacket = + constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5); + aaaManager.radiusListener.handleRadiusPacket(radiusCodeAccessChallengePacket); + + Ethernet radiusChallengeMD5Packet = (Ethernet) fetchPacket(2); + checkRadiusPacket(aaaManager, radiusChallengeMD5Packet, EAP.ATTR_MD5); + + // (4) Supplicant MD5 response + + Ethernet md5RadiusPacket = + constructSupplicantIdentifyPacket(stateMachine, + EAP.ATTR_MD5, + stateMachine.challengeIdentifier(), + radiusChallengeMD5Packet); + sendPacket(md5RadiusPacket); + + RADIUS responseMd5RadiusPacket = (RADIUS) fetchPacket(3); + + checkRadiusPacketFromSupplicant(responseMd5RadiusPacket); + assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 0)); + assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST)); + + // State machine should be in pending state + + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING)); + + // (5) RADIUS Success + + RADIUS successPacket = + constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS); + aaaManager.radiusListener.handleRadiusPacket((successPacket)); + Ethernet supplicantSuccessPacket = (Ethernet) fetchPacket(4); + + checkRadiusPacket(aaaManager, supplicantSuccessPacket, EAP.SUCCESS); + + // State machine should be in authorized state + + assertThat(stateMachine, notNullValue()); + assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED)); + + } + + /** + * Tests the default configuration. + */ + @Test + public void testConfig() { + assertThat(aaaManager.nasIpAddress.getHostAddress(), is(AaaConfig.DEFAULT_NAS_IP)); + assertThat(aaaManager.nasMacAddress, is(AaaConfig.DEFAULT_NAS_MAC)); + assertThat(aaaManager.radiusIpAddress.getHostAddress(), is(BAD_IP_ADDRESS)); + assertThat(aaaManager.radiusMacAddress, is(AaaConfig.DEFAULT_RADIUS_MAC)); + } +} diff --git a/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaTestBase.java b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaTestBase.java new file mode 100644 index 00000000..b076a2e3 --- /dev/null +++ b/framework/src/onos/apps/aaa/src/test/java/org/onosproject/aaa/AaaTestBase.java @@ -0,0 +1,224 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * 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.onosproject.aaa; + +import org.onlab.packet.BasePacket; +import org.onlab.packet.EAP; +import org.onlab.packet.EAPOL; +import org.onlab.packet.EthType; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.DefaultPacketContext; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketServiceAdapter; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.util.LinkedList; +import java.util.List; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.onosproject.net.NetTestTools.connectPoint; + +/** + * Common methods for AAA app testing. + */ +public class AaaTestBase { + + MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a"); + MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a"); + + // Our session id will be the device ID ("of:1") with the port ("1") concatenated + static final String SESSION_ID = "of:11"; + + List savedPackets = new LinkedList<>(); + PacketProcessor packetProcessor; + + /** + * Saves the given packet onto the saved packets list. + * + * @param packet packet to save + */ + void savePacket(BasePacket packet) { + savedPackets.add(packet); + } + + /** + * Keeps a reference to the PacketProcessor and saves the OutboundPackets. + */ + class MockPacketService extends PacketServiceAdapter { + + @Override + public void addProcessor(PacketProcessor processor, int priority) { + packetProcessor = processor; + } + + @Override + public void emit(OutboundPacket packet) { + try { + Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(), + 0, packet.data().array().length); + savePacket(eth); + } catch (Exception e) { + fail(e.getMessage()); + } + } + } + + /** + * Mocks the DefaultPacketContext. + */ + final class TestPacketContext extends DefaultPacketContext { + + private TestPacketContext(long time, InboundPacket inPkt, + OutboundPacket outPkt, boolean block) { + super(time, inPkt, outPkt, block); + } + + @Override + public void send() { + // We don't send anything out. + } + } + + /** + * Sends an Ethernet packet to the process method of the Packet Processor. + * + * @param reply Ethernet packet + */ + void sendPacket(Ethernet reply) { + final ByteBuffer byteBuffer = ByteBuffer.wrap(reply.serialize()); + InboundPacket inPacket = new DefaultInboundPacket(connectPoint("1", 1), + reply, + byteBuffer); + + PacketContext context = new TestPacketContext(127L, inPacket, null, false); + packetProcessor.process(context); + } + + /** + * Constructs an Ethernet packet containing identification payload. + * + * @return Ethernet packet + */ + Ethernet constructSupplicantIdentifyPacket(StateMachine stateMachine, + byte type, + byte id, + Ethernet radiusChallenge) + throws Exception { + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(clientMac.toBytes()); + eth.setSourceMACAddress(serverMac.toBytes()); + eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort()); + eth.setVlanID((short) 2); + + String username = "testuser"; + byte[] data = username.getBytes(); + + + if (type == EAP.ATTR_MD5) { + String password = "testpassword"; + EAPOL eapol = (EAPOL) radiusChallenge.getPayload(); + EAP eap = (EAP) eapol.getPayload(); + + byte[] identifier = new byte[password.length() + eap.getData().length]; + + identifier[0] = stateMachine.challengeIdentifier(); + System.arraycopy(password.getBytes(), 0, identifier, 1, password.length()); + System.arraycopy(eap.getData(), 1, identifier, 1 + password.length(), 16); + + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hash = md.digest(identifier); + data = new byte[17]; + data[0] = (byte) 16; + System.arraycopy(hash, 0, data, 1, 16); + } + EAP eap = new EAP(EAP.RESPONSE, (byte) 1, type, + data); + eap.setIdentifier(id); + + // eapol header + EAPOL eapol = new EAPOL(); + eapol.setEapolType(EAPOL.EAPOL_PACKET); + eapol.setPacketLength(eap.getLength()); + + // eap part + eapol.setPayload(eap); + + eth.setPayload(eapol); + eth.setPad(true); + return eth; + } + + /** + * Constructs an Ethernet packet containing a EAPOL_START Payload. + * + * @return Ethernet packet + */ + Ethernet constructSupplicantStartPacket() { + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(clientMac.toBytes()); + eth.setSourceMACAddress(serverMac.toBytes()); + eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort()); + eth.setVlanID((short) 2); + + EAP eap = new EAP(EAPOL.EAPOL_START, (byte) 2, EAPOL.EAPOL_START, null); + + // eapol header + EAPOL eapol = new EAPOL(); + eapol.setEapolType(EAPOL.EAPOL_START); + eapol.setPacketLength(eap.getLength()); + + // eap part + eapol.setPayload(eap); + + eth.setPayload(eapol); + eth.setPad(true); + return eth; + } + + /** + * Checks the contents of a RADIUS packet being sent to the RADIUS server. + * + * @param radiusPacket packet to check + * @param code expected code + */ + void checkRadiusPacket(AaaManager aaaManager, Ethernet radiusPacket, byte code) { + + assertThat(radiusPacket.getSourceMAC(), + is(MacAddress.valueOf(aaaManager.nasMacAddress))); + assertThat(radiusPacket.getDestinationMAC(), is(serverMac)); + + assertThat(radiusPacket.getPayload(), instanceOf(EAPOL.class)); + EAPOL eapol = (EAPOL) radiusPacket.getPayload(); + assertThat(eapol, notNullValue()); + + assertThat(eapol.getEapolType(), is(EAPOL.EAPOL_PACKET)); + assertThat(eapol.getPayload(), instanceOf(EAP.class)); + EAP eap = (EAP) eapol.getPayload(); + assertThat(eap, notNullValue()); + + assertThat(eap.getCode(), is(code)); + } +} -- cgit 1.2.3-korg