| /* |
| * |
| * 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 org.onlab.packet.MacAddress; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.xosintegration.VoltTenant; |
| import org.onosproject.xosintegration.VoltTenantService; |
| import org.slf4j.Logger; |
| |
| import java.util.BitSet; |
| |
| 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 final VoltTenantService voltService; |
| |
| private int identifier = -1; |
| private byte challengeIdentifier; |
| private byte[] challengeState; |
| private byte[] username; |
| private byte[] requestAuthenticator; |
| |
| // Supplicant connectivity info |
| protected ConnectPoint supplicantConnectpoint; |
| protected MacAddress supplicantAddress; |
| protected 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 | _ | _ | _ | _ |
| |
| 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_AUTHORIZED, 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; |
| |
| |
| /** |
| * State Machine Constructor. |
| * |
| * @param sessionId session Id represented by the switch dpid + port number |
| * @param voltService volt service reference |
| */ |
| public StateMachine(String sessionId, VoltTenantService voltService) { |
| log.info("Creating a new state machine for {}", sessionId); |
| this.sessionId = sessionId; |
| this.voltService = voltService; |
| |
| } |
| |
| /** |
| * Get the client id that is requesting for access. |
| * |
| * @return The client id. |
| */ |
| public String getSessionId() { |
| 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 = -1; |
| |
| 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; |
| } |
| |
| /** |
| * Get the challenge EAP identifier set by the RADIUS. |
| * |
| * @return The challenge EAP identifier. |
| */ |
| protected byte getChallengeIdentifier() { |
| 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; |
| } |
| |
| /** |
| * Get the challenge state set by the RADIUS. |
| * |
| * @return The challenge state. |
| */ |
| protected byte[] getChallengeState() { |
| 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; |
| } |
| |
| |
| /** |
| * Get the username. |
| * |
| * @return The requestAuthenticator. |
| */ |
| protected byte[] getReqeustAuthenticator() { |
| return this.requestAuthenticator; |
| } |
| |
| /** |
| * Set the username. |
| * |
| * @param authenticator The username sent to the RADIUS upon access request. |
| */ |
| protected void setRequestAuthenticator(byte[] authenticator) { |
| this.requestAuthenticator = authenticator; |
| } |
| |
| |
| /** |
| * Get the username. |
| * |
| * @return The username. |
| */ |
| protected byte[] getUsername() { |
| return this.username; |
| } |
| |
| /** |
| * Return the identifier of the state machine. |
| * |
| * @return The state machine identifier. |
| */ |
| public byte getIdentifier() { |
| 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 |
| */ |
| 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 { |
| try { |
| states[currentState].start(); |
| //move to the next state |
| next(TRANSITION_START); |
| createIdentifier(); |
| } catch (StateMachineInvalidTransitionException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * 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 { |
| try { |
| states[currentState].requestAccess(); |
| //move to the next state |
| next(TRANSITION_REQUEST_ACCESS); |
| } catch (StateMachineInvalidTransitionException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * RADIUS has accepted the identification. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void authorizeAccess() throws StateMachineException { |
| try { |
| states[currentState].radiusAccepted(); |
| //move to the next state |
| next(TRANSITION_AUTHORIZE_ACCESS); |
| |
| if (voltService != null) { |
| voltService.addTenant( |
| VoltTenant.builder() |
| .withHumanReadableName("VCPE-" + this.identifier) |
| .withId(this.identifier) |
| .withProviderService(1) |
| .withServiceSpecificId(String.valueOf(this.identifier)) |
| .withPort(this.supplicantConnectpoint) |
| .withVlanId(String.valueOf(this.vlanId)).build()); |
| } |
| |
| deleteIdentifier(); |
| } catch (StateMachineInvalidTransitionException e) { |
| e.printStackTrace(); |
| } |
| |
| } |
| |
| /** |
| * RADIUS has denied the identification. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void denyAccess() throws StateMachineException { |
| try { |
| states[currentState].radiusDenied(); |
| //move to the next state |
| next(TRANSITION_DENY_ACCESS); |
| deleteIdentifier(); |
| } catch (StateMachineInvalidTransitionException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Logoff request has been requested. |
| * Move to the next state if possible. |
| * |
| * @throws StateMachineException if authentication protocol is violated |
| */ |
| public void logoff() throws StateMachineException { |
| try { |
| states[currentState].logoff(); |
| //move to the next state |
| next(TRANSITION_LOGOFF); |
| } catch (StateMachineInvalidTransitionException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Get the current state. |
| * |
| * @return The current state. Could be STATE_IDLE, STATE_STARTED, STATE_PENDING, STATE_AUTHORIZED, |
| * STATE_UNAUTHORIZED. |
| */ |
| public int getState() { |
| return currentState; |
| } |
| |
| |
| public String toString() { |
| return ("sessionId: " + this.sessionId) + "\t" + ("identifier: " + this.identifier) + "\t" + |
| ("state: " + this.currentState); |
| } |
| } |
| |
| // FIXME: A source file should contain no more than one top-level entity! |
| |
| 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 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."); |
| } |
| } |
| |
| |
| /** |
| * Exception for the State Machine. |
| */ |
| class StateMachineException extends Exception { |
| public StateMachineException(String message) { |
| super(message); |
| |
| } |
| } |
| |
| /** |
| * Exception raised when the transition from one state to another is invalid. |
| */ |
| class StateMachineInvalidTransitionException extends StateMachineException { |
| public StateMachineInvalidTransitionException(String message) { |
| super(message); |
| } |
| } |