Ray Milkey
Committed by Gerrit Code Review

Support RADIUS server outside of the ONOS network

Change-Id: I7e64faae6831467e084db878e02023d40fb33f07
......@@ -15,10 +15,13 @@
*/
package org.onosproject.aaa;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
......@@ -31,19 +34,14 @@ import org.onlab.packet.EAPOL;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.RADIUS;
import org.onlab.packet.RADIUSAttribute;
import org.onlab.packet.TpPort;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
......@@ -53,7 +51,6 @@ 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.host.HostService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
......@@ -63,6 +60,8 @@ import org.onosproject.net.packet.PacketService;
import org.onosproject.xosintegration.VoltTenantService;
import org.slf4j.Logger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
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;
......@@ -85,10 +84,6 @@ public class AAA {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
// end host information
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected VoltTenantService voltTenantService;
......@@ -121,6 +116,12 @@ public class AAA {
// 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<ApplicationId, AAAConfig>(APP_SUBJECT_FACTORY,
......@@ -184,7 +185,16 @@ public class AAA {
StateMachine.initializeMaps();
hostService.startMonitoringIp(IpAddress.valueOf(radiusIpAddress));
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
......@@ -195,6 +205,24 @@ public class AAA {
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);
}
}
/**
......@@ -232,6 +260,19 @@ public class AAA {
packetService.cancelPackets(radSelector, 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
/**
......@@ -253,26 +294,11 @@ public class AAA {
case EAPOL:
handleSupplicantPacket(context.inPacket());
break;
case IPV4:
IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
Ip4Address srcIp = Ip4Address.valueOf(ipv4Packet.getSourceAddress());
Ip4Address radiusIp4Address = Ip4Address.valueOf(radiusIpAddress);
if (srcIp.equals(radiusIp4Address) && ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
// TODO: check for port as well when it's configurable
UDP udpPacket = (UDP) ipv4Packet.getPayload();
byte[] datagram = udpPacket.getPayload().serialize();
RADIUS radiusPacket;
radiusPacket = RADIUS.deserializer().deserialize(datagram, 0, datagram.length);
handleRadiusPacket(radiusPacket);
}
break;
default:
log.trace("Skipping Ethernet packet type {}",
EthType.EtherType.lookup(ethPkt.getEtherType()));
}
} catch (DeserializationException | StateMachineException e) {
} catch (StateMachineException e) {
log.warn("Unable to process RADIUS packet:", e);
}
}
......@@ -280,23 +306,26 @@ public class AAA {
/**
* Creates and initializes common fields of a RADIUS packet.
*
* @param identifier RADIUS identifier
* @param stateMachine state machine for the request
* @param eapPacket EAP packet
* @return RADIUS packet
*/
private RADIUS getRadiusPayload(byte identifier, EAP eapPacket) {
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,
eapPacket.getData());
stateMachine.username());
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
AAA.this.nasIpAddress.getAddress());
radiusPayload.encapsulateMessage(eapPacket);
radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
return radiusPayload;
}
......@@ -329,13 +358,13 @@ public class AAA {
//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(1L),
Ethernet eth = buildEapolResponse(srcMAC, MacAddress.valueOf(nasMacAddress),
ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
eapPayload);
stateMachine.setSupplicantAddress(srcMAC);
stateMachine.setVlanId(ethPkt.getVlanID());
this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
break;
case EAPOL.EAPOL_PACKET:
......@@ -350,11 +379,10 @@ public class AAA {
// request id access to RADIUS
stateMachine.setUsername(eapPacket.getData());
radiusPayload = getRadiusPayload(stateMachine.identifier(), eapPacket);
radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
// set Request Authenticator in StateMachine
stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
sendRadiusMessage(radiusPayload);
sendRADIUSPacket(radiusPayload);
// change the state to "PENDING"
stateMachine.requestAccess();
......@@ -365,22 +393,27 @@ public class AAA {
// machine.
if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) {
//send the RADIUS challenge response
radiusPayload = getRadiusPayload(stateMachine.challengeIdentifier(), eapPacket);
radiusPayload =
getRadiusPayload(stateMachine,
stateMachine.identifier(),
eapPacket);
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
stateMachine.challengeState());
sendRadiusMessage(radiusPayload);
radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
sendRADIUSPacket(radiusPayload);
}
break;
case EAP.ATTR_TLS:
// request id access to RADIUS
radiusPayload = getRadiusPayload(stateMachine.identifier(), eapPacket);
radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
stateMachine.challengeState());
stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
sendRadiusMessage(radiusPayload);
sendRADIUSPacket(radiusPayload);
radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
// TODO: this gets called on every fragment, should only be called at TLS-Start
stateMachine.requestAccess();
......@@ -392,14 +425,18 @@ public class AAA {
default:
log.trace("Skipping EAPOL message {}", eapol.getEapolType());
}
}
}
class RadiusListener implements Runnable {
/**
* Handles RADIUS packets.
*
* @param radiusPacket RADIUS packet coming from the RADIUS server.
*/
private void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException {
protected void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException {
StateMachine stateMachine = StateMachine.lookupStateMachineById(radiusPacket.getIdentifier());
if (stateMachine == null) {
log.error("Invalid session identifier, exiting...");
......@@ -410,13 +447,16 @@ public class AAA {
Ethernet eth;
switch (radiusPacket.getCode()) {
case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
byte[] challengeState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue();
byte[] challengeState =
radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue();
eapPayload = radiusPacket.decapsulateMessage();
stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
eth = buildEapolResponse(stateMachine.supplicantAddress(),
MacAddress.valueOf(1L), stateMachine.vlanId(), EAPOL.EAPOL_PACKET,
MacAddress.valueOf(nasMacAddress),
stateMachine.vlanId(),
EAPOL.EAPOL_PACKET,
eapPayload);
this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
break;
case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
//send an EAPOL - Success to the supplicant.
......@@ -425,9 +465,11 @@ public class AAA {
eapPayload = new EAP();
eapPayload = (EAP) eapPayload.deserialize(eapMessage, 0, eapMessage.length);
eth = buildEapolResponse(stateMachine.supplicantAddress(),
MacAddress.valueOf(1L), stateMachine.vlanId(), EAPOL.EAPOL_PACKET,
MacAddress.valueOf(nasMacAddress),
stateMachine.vlanId(),
EAPOL.EAPOL_PACKET,
eapPayload);
this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
stateMachine.authorizeAccess();
break;
......@@ -439,59 +481,43 @@ public class AAA {
}
}
private void sendRadiusMessage(RADIUS radiusMessage) {
Set<Host> hosts = hostService.getHostsByIp(IpAddress.valueOf(radiusIpAddress));
Optional<Host> odst = hosts.stream().filter(h -> h.vlan().toShort() == VlanId.UNTAGGED).findFirst();
Host dst;
if (!odst.isPresent()) {
log.info("Radius server {} is not present", radiusIpAddress);
return;
} else {
dst = odst.get();
}
UDP udp = new UDP();
IPv4 ip4Packet = new IPv4();
Ethernet ethPkt = new Ethernet();
radiusMessage.setParent(udp);
udp.setDestinationPort(radiusServerPort);
udp.setSourcePort(radiusServerPort);
udp.setPayload(radiusMessage);
udp.setParent(ip4Packet);
ip4Packet.setSourceAddress(AAA.this.nasIpAddress.getHostAddress());
ip4Packet.setDestinationAddress(AAA.this.radiusIpAddress.getHostAddress());
ip4Packet.setProtocol(IPv4.PROTOCOL_UDP);
ip4Packet.setPayload(udp);
ip4Packet.setParent(ethPkt);
ethPkt.setDestinationMACAddress(radiusMacAddress);
ethPkt.setSourceMACAddress(nasMacAddress);
ethPkt.setEtherType(Ethernet.TYPE_IPV4);
ethPkt.setPayload(ip4Packet);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(PortNumber.portNumber(radiusPort)).build();
OutboundPacket packet = new DefaultOutboundPacket(DeviceId.deviceId(radiusSwitch),
treatment, ByteBuffer.wrap(ethPkt.serialize()));
packetService.emit(packet);
}
@Override
public void run() {
boolean done = false;
int packetNumber = 1;
log.info("UDP listener thread starting up");
RADIUS inboundRadiusPacket;
while (!done) {
try {
DatagramPacket inboundBasePacket = new DatagramPacket(new byte[1000], 1000);
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);
}
/**
* 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);
} catch (IOException e) {
log.info("Socket was closed, exiting listener thread");
done = true;
}
}
}
}
RadiusListener radiusListener = new RadiusListener();
private class InternalConfigListener implements NetworkConfigListener {
/**
......
......@@ -37,13 +37,13 @@ public class AAAConfig extends Config<ApplicationId> {
private static final String RADIUS_PORT = "radiusPort";
// RADIUS server IP address
protected static final String DEFAULT_RADIUS_IP = "192.168.1.10";
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 = "192.168.1.11";
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";
......
/*
* 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 AAA aaa;
/**
* Mocks the network config registry.
*/
@SuppressWarnings("unchecked")
static final class TestNetworkConfigRegistry
extends NetworkConfigRegistryAdapter {
@Override
public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
return (C) new AAAConfig();
}
}
/**
* Sets up the services required by the AAA application.
*/
@Before
public void setUp() {
aaa = new AAA();
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));
}
}
......@@ -15,164 +15,61 @@
*/
package org.onosproject.aaa;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.Data;
import org.onlab.packet.BasePacket;
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.IPv4;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.RADIUS;
import org.onlab.packet.RADIUSAttribute;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
import org.onosproject.core.CoreServiceAdapter;
import org.onosproject.net.Annotations;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.NetworkConfigRegistryAdapter;
import org.onosproject.net.host.HostServiceAdapter;
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 org.onosproject.net.provider.ProviderId;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
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;
/**
* Set of tests of the ONOS application component.
*/
public class AAATest {
public class AAATest extends AAATestBase {
MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a");
static final String BAD_IP_ADDRESS = "198.51.100.0";
PacketProcessor packetProcessor;
private AAA aaa;
List<Ethernet> savedPackets = new LinkedList<>();
/**
* Saves the given packet onto the saved packets list.
*
* @param eth packet to save
*/
private void savePacket(Ethernet eth) {
savedPackets.add(eth);
class AAAWithoutRadiusServer extends AAA {
protected void sendRADIUSPacket(RADIUS radiusPacket) {
savePacket(radiusPacket);
}
}
/**
* Keeps a reference to the PacketProcessor and saves the OutboundPackets.
* Mocks the AAAConfig class to force usage of an unroutable address for the
* RADIUS server.
*/
private class MockPacketService extends PacketServiceAdapter {
@Override
public void addProcessor(PacketProcessor processor, int priority) {
packetProcessor = processor;
}
static class MockAAAConfig extends AAAConfig {
@Override
public void emit(OutboundPacket packet) {
public InetAddress radiusIp() {
try {
Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(),
0, packet.data().array().length);
savePacket(eth);
} catch (Exception e) {
fail(e.getMessage());
return InetAddress.getByName(BAD_IP_ADDRESS);
} catch (UnknownHostException ex) {
// can't happen
throw new IllegalStateException(ex);
}
}
}
/**
* Mocks the DefaultPacketContext.
*/
private 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.
}
}
/**
* Mocks a host to allow locating the Radius server.
*/
private static final class MockHost implements Host {
@Override
public HostId id() {
return null;
}
@Override
public MacAddress mac() {
return null;
}
@Override
public VlanId vlan() {
return VlanId.vlanId(VlanId.UNTAGGED);
}
@Override
public Set<IpAddress> ipAddresses() {
return null;
}
@Override
public HostLocation location() {
return null;
}
@Override
public Annotations annotations() {
return null;
}
@Override
public ProviderId providerId() {
return null;
}
}
/**
* Mocks the Host service.
*/
private static final class MockHostService extends HostServiceAdapter {
@Override
public Set<Host> getHostsByIp(IpAddress ip) {
return ImmutableSet.of(new MockHost());
}
}
/**
* Mocks the network config registry.
*/
@SuppressWarnings("unchecked")
......@@ -180,83 +77,12 @@ public class AAATest {
extends NetworkConfigRegistryAdapter {
@Override
public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
return (C) new AAAConfig();
AAAConfig aaaConfig = new MockAAAConfig();
return (C) aaaConfig;
}
}
/**
* Sends an Ethernet packet to the process method of the Packet Processor.
*
* @param reply Ethernet packet
*/
private 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 a EAPOL_START Payload.
*
* @return Ethernet packet
*/
private 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) 1, 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;
}
/**
* Constructs an Ethernet packet containing identification payload.
*
* @return Ethernet packet
*/
private Ethernet constructSupplicantIdentifyPacket(byte type) {
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 = "user";
EAP eap = new EAP(EAP.REQUEST, (byte) 1, type,
username.getBytes(Charsets.US_ASCII));
eap.setIdentifier((byte) 1);
// 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 RADIUS challenge
* packet.
*
......@@ -264,18 +90,9 @@ public class AAATest {
* @param challengeType type to use in challenge packet
* @return Ethernet packet
*/
private Ethernet constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
Ethernet eth = new Ethernet();
eth.setDestinationMACAddress(clientMac.toBytes());
eth.setSourceMACAddress(serverMac.toBytes());
eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
eth.setVlanID((short) 2);
IPv4 ipv4 = new IPv4();
ipv4.setProtocol(IPv4.PROTOCOL_UDP);
ipv4.setSourceAddress(aaa.radiusIpAddress.getHostAddress());
private RADIUS constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
String challenge = "1234";
String challenge = "12345678901234567";
EAP eap = new EAP(challengeType, (byte) 1, challengeType,
challenge.getBytes(Charsets.US_ASCII));
......@@ -291,13 +108,7 @@ public class AAATest {
radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
eap.serialize());
UDP udp = new UDP();
udp.setPayload(radius);
ipv4.setPayload(udp);
eth.setPayload(ipv4);
eth.setPad(true);
return eth;
return radius;
}
/**
......@@ -305,11 +116,10 @@ public class AAATest {
*/
@Before
public void setUp() {
aaa = new AAA();
aaa = new AAAWithoutRadiusServer();
aaa.netCfgService = new TestNetworkConfigRegistry();
aaa.coreService = new CoreServiceAdapter();
aaa.packetService = new MockPacketService();
aaa.hostService = new MockHostService();
aaa.activate();
}
......@@ -324,60 +134,16 @@ public class AAATest {
/**
* Extracts the RADIUS packet from a packet sent by the supplicant.
*
* @param supplicantPacket packet sent by the supplicant
* @return RADIUS packet
* @param radius RADIUS packet sent by the supplicant
* @throws DeserializationException if deserialization of the packet contents
* fails.
*/
private RADIUS checkAndFetchRADIUSPacketFromSupplicant(Ethernet supplicantPacket)
private void checkRADIUSPacketFromSupplicant(RADIUS radius)
throws DeserializationException {
assertThat(supplicantPacket, notNullValue());
assertThat(supplicantPacket.getVlanID(), is(VlanId.UNTAGGED));
assertThat(supplicantPacket.getSourceMAC().toString(), is(aaa.nasMacAddress));
assertThat(supplicantPacket.getDestinationMAC().toString(), is(aaa.radiusMacAddress));
assertThat(supplicantPacket.getPayload(), instanceOf(IPv4.class));
IPv4 ipv4 = (IPv4) supplicantPacket.getPayload();
assertThat(ipv4, notNullValue());
assertThat(IpAddress.valueOf(ipv4.getSourceAddress()).toString(),
is(aaa.nasIpAddress.getHostAddress()));
assertThat(IpAddress.valueOf(ipv4.getDestinationAddress()).toString(),
is(aaa.radiusIpAddress.getHostAddress()));
assertThat(ipv4.getPayload(), instanceOf(UDP.class));
UDP udp = (UDP) ipv4.getPayload();
assertThat(udp, notNullValue());
assertThat(udp.getPayload(), instanceOf(Data.class));
Data data = (Data) udp.getPayload();
RADIUS radius = RADIUS.deserializer()
.deserialize(data.getData(), 0, data.getData().length);
assertThat(radius, notNullValue());
return radius;
}
/**
* Checks the contents of a RADIUS packet being sent to the RADIUS server.
*
* @param radiusPacket packet to check
* @param code expected code
*/
private void checkRadiusPacket(Ethernet radiusPacket, byte code) {
assertThat(radiusPacket.getVlanID(), is((short) 2));
// TODO: These address values seem wrong, but are produced by the current AAA implementation
assertThat(radiusPacket.getSourceMAC(), is(MacAddress.valueOf(1L)));
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();
EAP eap = radius.decapsulateMessage();
assertThat(eap, notNullValue());
assertThat(eap.getCode(), is(code));
}
/**
......@@ -387,11 +153,10 @@ public class AAATest {
* @param index index into sent packets array
* @return packet
*/
private Ethernet fetchPacket(int index) {
assertThat(savedPackets.size(), is(index + 1));
Ethernet eth = savedPackets.get(index);
assertThat(eth, notNullValue());
return eth;
private BasePacket fetchPacket(int index) {
BasePacket packet = savedPackets.get(index);
assertThat(packet, notNullValue());
return packet;
}
/**
......@@ -400,61 +165,64 @@ public class AAATest {
* @throws DeserializationException if packed deserialization fails.
*/
@Test
public void testAuthentication() throws DeserializationException {
// Our session id will be the device ID ("of:1") with the port ("1") concatenated
String sessionId = "of:11";
public void testAuthentication() throws Exception {
// (1) Supplicant start up
Ethernet startPacket = constructSupplicantStartPacket();
sendPacket(startPacket);
Ethernet responsePacket = fetchPacket(0);
checkRadiusPacket(responsePacket, EAP.ATTR_IDENTITY);
Ethernet responsePacket = (Ethernet) fetchPacket(0);
checkRadiusPacket(aaa, responsePacket, EAP.ATTR_IDENTITY);
// (2) Supplicant identify
Ethernet identifyPacket = constructSupplicantIdentifyPacket(EAP.ATTR_IDENTITY);
Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
sendPacket(identifyPacket);
Ethernet radiusIdentifyPacket = fetchPacket(1);
RADIUS radiusIdentifyPacket = (RADIUS) fetchPacket(1);
checkRADIUSPacketFromSupplicant(radiusIdentifyPacket);
RADIUS radiusAccessRequest = checkAndFetchRADIUSPacketFromSupplicant(radiusIdentifyPacket);
assertThat(radiusAccessRequest, notNullValue());
assertThat(radiusAccessRequest.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
assertThat(new String(radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
is("user"));
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,
radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
.getValue());
assertThat(nasIp.toString(), is(aaa.nasIpAddress.getHostAddress()));
// State machine should have been created by now
StateMachine stateMachine =
StateMachine.lookupStateMachineBySessionId(sessionId);
StateMachine.lookupStateMachineBySessionId(SESSION_ID);
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
// (3) RADIUS MD5 challenge
Ethernet radiusCodeAccessChallengePacket =
RADIUS radiusCodeAccessChallengePacket =
constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
sendPacket(radiusCodeAccessChallengePacket);
aaa.radiusListener.handleRadiusPacket(radiusCodeAccessChallengePacket);
Ethernet radiusChallengeMD5Packet = fetchPacket(2);
checkRadiusPacket(radiusChallengeMD5Packet, EAP.ATTR_MD5);
Ethernet radiusChallengeMD5Packet = (Ethernet) fetchPacket(2);
checkRadiusPacket(aaa, radiusChallengeMD5Packet, EAP.ATTR_MD5);
// (4) Supplicant MD5 response
Ethernet md5RadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_MD5);
Ethernet md5RadiusPacket =
constructSupplicantIdentifyPacket(stateMachine,
EAP.ATTR_MD5,
stateMachine.challengeIdentifier(),
radiusChallengeMD5Packet);
sendPacket(md5RadiusPacket);
Ethernet supplicantMD5ResponsePacket = fetchPacket(3);
RADIUS responseMd5RadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantMD5ResponsePacket);
assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 1));
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
......@@ -462,37 +230,20 @@ public class AAATest {
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
// (5) RADIUS TLS Challenge
Ethernet radiusCodeAccessChallengeTLSPacket =
constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_TLS);
sendPacket(radiusCodeAccessChallengeTLSPacket);
// (5) RADIUS Success
Ethernet radiusChallengeTLSPacket = fetchPacket(4);
checkRadiusPacket(radiusChallengeTLSPacket, EAP.ATTR_TLS);
// (6) Supplicant TLS response
Ethernet tlsRadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_TLS);
sendPacket(tlsRadiusPacket);
Ethernet supplicantTLSResponsePacket = fetchPacket(5);
RADIUS responseTLSRadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantTLSResponsePacket);
assertThat(responseTLSRadiusPacket.getIdentifier(), is((byte) 0));
assertThat(responseTLSRadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
// (7) RADIUS Success
Ethernet successPacket =
RADIUS successPacket =
constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
sendPacket(successPacket);
Ethernet supplicantSuccessPacket = fetchPacket(6);
aaa.radiusListener.handleRadiusPacket((successPacket));
Ethernet supplicantSuccessPacket = (Ethernet) fetchPacket(4);
checkRadiusPacket(supplicantSuccessPacket, EAP.SUCCESS);
checkRadiusPacket(aaa, supplicantSuccessPacket, EAP.SUCCESS);
// State machine should be in authorized state
assertThat(stateMachine, notNullValue());
assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
}
/**
......@@ -502,7 +253,7 @@ public class AAATest {
public void testConfig() {
assertThat(aaa.nasIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_NAS_IP));
assertThat(aaa.nasMacAddress, is(AAAConfig.DEFAULT_NAS_MAC));
assertThat(aaa.radiusIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_RADIUS_IP));
assertThat(aaa.radiusIpAddress.getHostAddress(), is(BAD_IP_ADDRESS));
assertThat(aaa.radiusMacAddress, is(AAAConfig.DEFAULT_RADIUS_MAC));
}
}
......
/*
* 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 java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.LinkedList;
import java.util.List;
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 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<BasePacket> 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(AAA aaa, Ethernet radiusPacket, byte code) {
assertThat(radiusPacket.getSourceMAC(),
is(MacAddress.valueOf(aaa.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));
}
}
......@@ -21,7 +21,9 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class StateMachineTest {
......