| /* |
| * |
| * 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 java.util.BitSet; |
| import java.util.Map; |
| |
| import org.onlab.packet.MacAddress; |
| import org.onosproject.net.ConnectPoint; |
| import org.slf4j.Logger; |
| |
| import com.google.common.collect.Maps; |
| |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * AAA Finite State Machine. |
| */ |
| |
| class StateMachine { |
| //INDEX to identify the state in the transition table |
| static final int STATE_IDLE = 0; |
| static final int STATE_STARTED = 1; |
| static final int STATE_PENDING = 2; |
| static final int STATE_AUTHORIZED = 3; |
| static final int STATE_UNAUTHORIZED = 4; |
| |
| //INDEX to identify the transition in the transition table |
| static final int TRANSITION_START = 0; // --> started |
| static final int TRANSITION_REQUEST_ACCESS = 1; |
| static final int TRANSITION_AUTHORIZE_ACCESS = 2; |
| static final int TRANSITION_DENY_ACCESS = 3; |
| static final int TRANSITION_LOGOFF = 4; |
| |
| //map of access identifiers (issued at EAPOL START) |
| static BitSet bitSet = new BitSet(); |
| |
| private int identifier = -1; |
| private byte challengeIdentifier; |
| private byte[] challengeState; |
| private byte[] username; |
| private byte[] requestAuthenticator; |
| |
| // Supplicant connectivity info |
| private ConnectPoint supplicantConnectpoint; |
| private MacAddress supplicantAddress; |
| private short vlanId; |
| |
| private String sessionId = null; |
| |
| private final Logger log = getLogger(getClass()); |
| |
| |
| private State[] states = { |
| new Idle(), new Started(), new Pending(), new Authorized(), new Unauthorized() |
| }; |
| |
| |
| //State transition table |
| /* |
| |
| state IDLE | STARTED | PENDING | AUTHORIZED | UNAUTHORIZED |
| //// |
| input |
| ---------------------------------------------------------------------------------------------------- |
| |
| START STARTED | _ | _ | STARTED | _ |
| |
| REQUEST_ACCESS _ | PENDING | _ | _ | _ |
| |
| AUTHORIZE_ACCESS _ | _ | AUTHORIZED | _ | _ |
| |
| DENY_ACCESS _ | - | UNAUTHORIZED | _ | _ |
| |
| LOGOFF _ | _ | _ | IDLE | IDLE |
| */ |
| |
| private int[] idleTransition = |
| {STATE_STARTED, STATE_IDLE, STATE_IDLE, STATE_IDLE, STATE_IDLE}; |
| private int[] startedTransition = |
| {STATE_STARTED, STATE_PENDING, STATE_STARTED, STATE_STARTED, STATE_STARTED}; |
| private int[] pendingTransition = |
| {STATE_PENDING, STATE_PENDING, STATE_AUTHORIZED, STATE_UNAUTHORIZED, STATE_PENDING}; |
| private int[] authorizedTransition = |
| {STATE_STARTED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_IDLE}; |
| private int[] unauthorizedTransition = |
| {STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_IDLE}; |
| |
| //THE TRANSITION TABLE |
| private int[][] transition = |
| {idleTransition, startedTransition, pendingTransition, authorizedTransition, |
| unauthorizedTransition}; |
| |
| private int currentState = STATE_IDLE; |
| |
| // Maps of state machines. Each state machine is represented by an |
| // unique identifier on the switch: dpid + port number |
| private static Map<String, StateMachine> sessionIdMap; |
| private static Map<Integer, StateMachine> identifierMap; |
| |
| public static void initializeMaps() { |
| sessionIdMap = Maps.newConcurrentMap(); |
| identifierMap = Maps.newConcurrentMap(); |
| } |
| |
| public static void destroyMaps() { |
| sessionIdMap = null; |
| identifierMap = null; |
| } |
| |
| public static Map<String, StateMachine> sessionIdMap() { |
| return sessionIdMap; |
| } |
| |
| public static StateMachine lookupStateMachineById(byte identifier) { |
| return identifierMap.get((int) identifier); |
| } |
| |
| public static StateMachine lookupStateMachineBySessionId(String sessionId) { |
| return sessionIdMap.get(sessionId); |
| } /** |
| * State Machine Constructor. |
| * |
| * @param sessionId session Id represented by the switch dpid + port number |
| */ |
| public StateMachine(String sessionId) { |
| log.info("Creating a new state machine for {}", sessionId); |
| this.sessionId = sessionId; |
| sessionIdMap.put(sessionId, this); |
| } |
| |
| /** |
| * Gets the connect point for the supplicant side. |
| * |
| * @return supplicant connect point |
| */ |
| public ConnectPoint supplicantConnectpoint() { |
| return supplicantConnectpoint; |
| } |
| |
| /** |
| * Sets the supplicant side connect point. |
| * |
| * @param supplicantConnectpoint supplicant select point. |
| */ |
| public void setSupplicantConnectpoint(ConnectPoint supplicantConnectpoint) { |
| this.supplicantConnectpoint = supplicantConnectpoint; |
| } |
| |
| /** |
| * Gets the MAC address of the supplicant. |
| * |
| * @return supplicant MAC address |
| */ |
| public MacAddress supplicantAddress() { |
| return supplicantAddress; |
| } |
| |
| /** |
| * Sets the supplicant MAC address. |
| * |
| * @param supplicantAddress new supplicant MAC address |
| */ |
| public void setSupplicantAddress(MacAddress supplicantAddress) { |
| this.supplicantAddress = supplicantAddress; |
| } |
| |
| /** |
| * Gets the client's Vlan ID. |
| * |
| * @return client vlan ID |
| */ |
| public short vlanId() { |
| return vlanId; |
| } |
| |
| /** |
| * Sets the client's vlan ID. |
| * |
| * @param vlanId new client vlan ID |
| */ |
| public void setVlanId(short vlanId) { |
| this.vlanId = vlanId; |
| } |
| |
| /** |
| * Gets the client id that is requesting for access. |
| * |
| * @return The client id. |
| */ |
| public String sessionId() { |
| return this.sessionId; |
| } |
| |
| /** |
| * Create the identifier for the state machine (happens when goes to STARTED state). |
| */ |
| private void createIdentifier() throws StateMachineException { |
| log.debug("Creating Identifier."); |
| int index; |
| |
| try { |
| //find the first available spot for identifier assignment |
| index = StateMachine.bitSet.nextClearBit(0); |
| |
| //there is a limit of 256 identifiers |
| if (index == 256) { |
| throw new StateMachineException("Cannot handle any new identifier. Limit is 256."); |
| } |
| } catch (IndexOutOfBoundsException e) { |
| throw new StateMachineException(e.getMessage()); |
| } |
| |
| log.info("Assigning identifier {}", index); |
| StateMachine.bitSet.set(index); |
| this.identifier = index; |
| } |
| |
| /** |
| * Set the challenge identifier and the state issued by the RADIUS. |
| * |
| * @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message. |
| * @param challengeState The challenge state from the RADIUS. |
| */ |
| protected void setChallengeInfo(byte challengeIdentifier, byte[] challengeState) { |
| this.challengeIdentifier = challengeIdentifier; |
| this.challengeState = challengeState; |
| } |
| |
| /** |
| * Set the challenge identifier issued by the RADIUS on the access challenge request. |
| * |
| * @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message. |
| */ |
| protected void setChallengeIdentifier(byte challengeIdentifier) { |
| log.info("Set Challenge Identifier to {}", challengeIdentifier); |
| this.challengeIdentifier = challengeIdentifier; |
| } |
| |
| /** |
| * Gets the challenge EAP identifier set by the RADIUS. |
| * |
| * @return The challenge EAP identifier. |
| */ |
| protected byte challengeIdentifier() { |
| return this.challengeIdentifier; |
| } |
| |
| |
| /** |
| * Set the challenge state info issued by the RADIUS. |
| * |
| * @param challengeState The challenge state from the RADIUS. |
| */ |
| protected void setChallengeState(byte[] challengeState) { |
| log.info("Set Challenge State"); |
| this.challengeState = challengeState; |
| } |
| |
| /** |
| * Gets the challenge state set by the RADIUS. |
| * |
| * @return The challenge state. |
| */ |
| protected byte[] challengeState() { |
| return this.challengeState; |
| } |
| |
| /** |
| * Set the username. |
| * |
| * @param username The username sent to the RADIUS upon access request. |
| */ |
| protected void setUsername(byte[] username) { |
| this.username = username; |
| } |
| |
| |
| /** |
| * Gets the username. |
| * |
| * @return The requestAuthenticator. |
| */ |
| protected byte[] requestAuthenticator() { |
| return this.requestAuthenticator; |
| } |
| |
| /** |
| * Sets the authenticator. |
| * |
| * @param authenticator The username sent to the RADIUS upon access request. |
| */ |
| protected void setRequestAuthenticator(byte[] authenticator) { |
| this.requestAuthenticator = authenticator; |
| } |
| |
| |
| /** |
| * Gets the username. |
| * |
| * @return The username. |
| */ |
| protected byte[] username() { |
| return this.username; |
| } |
| |
| /** |
| * Return the identifier of the state machine. |
| * |
| * @return The state machine identifier. |
| */ |
| public byte identifier() { |
| return (byte) this.identifier; |
| } |
| |
| |
| protected void deleteIdentifier() { |
| if (this.identifier != -1) { |
| log.info("Freeing up " + this.identifier); |
| //this state machine should be deleted and free up the identifier |
| StateMachine.bitSet.clear(this.identifier); |
| this.identifier = -1; |
| } |
| } |
| |
| |
| /** |
| * Move to the next state. |
| * |
| * @param msg message |
| */ |
| private void next(int msg) { |
| currentState = transition[currentState][msg]; |
| log.info("Current State " + currentState); |
| } |
| |
| /** |
| * Client has requested the start action to allow network access. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void start() throws StateMachineException { |
| states[currentState].start(); |
| //move to the next state |
| next(TRANSITION_START); |
| createIdentifier(); |
| identifierMap.put(identifier, this); |
| } |
| |
| /** |
| * An Identification information has been sent by the supplicant. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void requestAccess() throws StateMachineException { |
| states[currentState].requestAccess(); |
| //move to the next state |
| next(TRANSITION_REQUEST_ACCESS); |
| } |
| |
| /** |
| * RADIUS has accepted the identification. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void authorizeAccess() throws StateMachineException { |
| states[currentState].radiusAccepted(); |
| //move to the next state |
| next(TRANSITION_AUTHORIZE_ACCESS); |
| |
| // TODO: put in calls to launch vSG here |
| |
| deleteIdentifier(); |
| } |
| |
| /** |
| * RADIUS has denied the identification. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void denyAccess() throws StateMachineException { |
| states[currentState].radiusDenied(); |
| //move to the next state |
| next(TRANSITION_DENY_ACCESS); |
| deleteIdentifier(); |
| } |
| |
| /** |
| * Logoff request has been requested. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void logoff() throws StateMachineException { |
| states[currentState].logoff(); |
| //move to the next state |
| next(TRANSITION_LOGOFF); |
| } |
| |
| /** |
| * Gets the current state. |
| * |
| * @return The current state. Could be STATE_IDLE, STATE_STARTED, STATE_PENDING, STATE_AUTHORIZED, |
| * STATE_UNAUTHORIZED. |
| */ |
| public int state() { |
| return currentState; |
| } |
| |
| @Override |
| public String toString() { |
| return ("sessionId: " + this.sessionId) + "\t" + ("identifier: " + this.identifier) + "\t" + |
| ("state: " + this.currentState); |
| } |
| |
| abstract class State { |
| private final Logger log = getLogger(getClass()); |
| |
| private String name = "State"; |
| |
| public void start() throws StateMachineInvalidTransitionException { |
| log.warn("START transition from this state is not allowed."); |
| } |
| |
| public void requestAccess() throws StateMachineInvalidTransitionException { |
| log.warn("REQUEST ACCESS transition from this state is not allowed."); |
| } |
| |
| public void radiusAccepted() throws StateMachineInvalidTransitionException { |
| log.warn("AUTHORIZE ACCESS transition from this state is not allowed."); |
| } |
| |
| public void radiusDenied() throws StateMachineInvalidTransitionException { |
| log.warn("DENY ACCESS transition from this state is not allowed."); |
| } |
| |
| public void logoff() throws StateMachineInvalidTransitionException { |
| log.warn("LOGOFF transition from this state is not allowed."); |
| } |
| } |
| |
| /** |
| * Idle state: supplicant is logged of from the network. |
| */ |
| class Idle extends State { |
| private final Logger log = getLogger(getClass()); |
| private String name = "IDLE_STATE"; |
| |
| public void start() { |
| log.info("Moving from IDLE state to STARTED state."); |
| } |
| } |
| |
| /** |
| * Started state: supplicant has entered the network and informed the authenticator. |
| */ |
| class Started extends State { |
| private final Logger log = getLogger(getClass()); |
| private String name = "STARTED_STATE"; |
| |
| public void requestAccess() { |
| log.info("Moving from STARTED state to PENDING state."); |
| } |
| } |
| |
| /** |
| * Pending state: supplicant has been identified by the authenticator but has not access yet. |
| */ |
| class Pending extends State { |
| private final Logger log = getLogger(getClass()); |
| private String name = "PENDING_STATE"; |
| |
| public void radiusAccepted() { |
| log.info("Moving from PENDING state to AUTHORIZED state."); |
| } |
| |
| public void radiusDenied() { |
| log.info("Moving from PENDING state to UNAUTHORIZED state."); |
| } |
| } |
| |
| /** |
| * Authorized state: supplicant port has been accepted, access is granted. |
| */ |
| class Authorized extends State { |
| private final Logger log = getLogger(getClass()); |
| private String name = "AUTHORIZED_STATE"; |
| |
| public void start() { |
| log.info("Moving from AUTHORIZED state to STARTED state."); |
| } |
| |
| public void logoff() { |
| |
| log.info("Moving from AUTHORIZED state to IDLE state."); |
| } |
| } |
| |
| /** |
| * Unauthorized state: supplicant port has been rejected, access is denied. |
| */ |
| class Unauthorized extends State { |
| private final Logger log = getLogger(getClass()); |
| private String name = "UNAUTHORIZED_STATE"; |
| |
| public void logoff() { |
| log.info("Moving from UNAUTHORIZED state to IDLE state."); |
| } |
| } |
| |
| |
| } |