/* * ConnectionManager.java * Created on 2011-08-24 * * Copyright (c) Verax Systems 2011. * All rights reserved. * * This software is furnished under a license. Use, duplication, * disclosure and all other uses are restricted to the rights * specified in the written license agreement. */ package com.veraxsystems.vxipmi.connection; import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import com.veraxsystems.vxipmi.coding.commands.PrivilegeLevel; import com.veraxsystems.vxipmi.coding.commands.session.GetChannelAuthenticationCapabilitiesResponseData; import com.veraxsystems.vxipmi.coding.security.CipherSuite; import com.veraxsystems.vxipmi.common.PropertiesManager; import com.veraxsystems.vxipmi.transport.Messenger; import com.veraxsystems.vxipmi.transport.UdpListener; import com.veraxsystems.vxipmi.transport.UdpMessenger; /** * Manages multiple {@link Connection}s */ public class ConnectionManager { private Messenger messenger; private List connections; private static Integer sessionId = 100; private static Integer sessionlessTag = 0; private static List reservedTags = new ArrayList(); /** * Frequency of the no-op commands that will be sent to keep up the session */ private static int pingPeriod = -1; /** * Initiates the connection manager. Wildcard IP address will be used. * * @param port * - the port at which {@link UdpListener} will work * @throws IOException * when properties file was not found */ public ConnectionManager(int port) throws IOException { messenger = new UdpMessenger(port); initialize(); } /** * Initiates the connection manager. * * @param port * - the port at which {@link UdpListener} will work * @param address * - the IP interface {@link UdpListener} will bind to * @throws IOException * when properties file was not found */ public ConnectionManager(int port, InetAddress address) throws IOException { messenger = new UdpMessenger(port, address); initialize(); } /** * Initiates the connection manager. * * @param messenger * - {@link Messenger} to be used in communication * @throws IOException */ public ConnectionManager(Messenger messenger) throws IOException { this.messenger = messenger; initialize(); } private void initialize() throws IOException { connections = new ArrayList(); reservedTags = new ArrayList(); if (pingPeriod == -1) { pingPeriod = Integer.parseInt(PropertiesManager.getInstance().getProperty("pingPeriod")); } } /** * Closes all open connections and disconnects {@link UdpListener}. */ public void close() { synchronized (connections) { for (Connection connection : connections) { if (connection != null && connection.isActive()) { connection.disconnect(); } } } messenger.closeConnection(); } /** * The session ID generated by the {@link ConnectionManager}. * Auto-incremented. */ public static synchronized int generateSessionId() { synchronized (sessionId) { sessionId %= (Integer.MAX_VALUE / 4); return sessionId++; } } /** * The tag for messages sent outside the session generated by the * {@link ConnectionManager}. Auto-incremented. */ public static synchronized int generateSessionlessTag() { synchronized (sessionlessTag) { boolean wait = true; while (wait) { ++sessionlessTag; sessionlessTag %= 60; synchronized (reservedTags) { if (!reservedTags.contains(sessionlessTag)) { wait = false; } } if (wait) { try { Thread.sleep(1); } catch (InterruptedException e) { // TODO log } } } synchronized (reservedTags) { reservedTags.add(sessionlessTag); } return sessionlessTag; } } /** * Frees the sessionless tag for further use * * @param tag * - tag to free */ public static void freeTag(int tag) { synchronized (reservedTags) { reservedTags.remove((Integer) tag); } } /** * Returns {@link Connection} identified by index. * * @param index * - index of the connection to return */ public Connection getConnection(int index) { return connections.get(index); } /** * Closes the connection with the given index. */ public void closeConnection(int index) { connections.get(index).disconnect(); } /** * Returns first {@link Connection} associated with the address * * @param address * - {@link InetAddress} of the remote host to get connection * with. * @return First {@link Connection} to the address or null if none found */ public Connection getConnection(InetAddress address) { synchronized (connections) { for (Connection connection : connections) { if (connection != null && connection.isActive() && connection.getRemoteMachineAddress() == address) { return connection; } } } return null; } /** * Creates and initiates {@link Connection} to the remote host. * * @param address * - {@link InetAddress} of the remote host * @param pingPeriod * - frequency of the no-op commands that will be sent to keep up * the session * @return index of the connection * @throws IOException * - when properties file was not found * @throws FileNotFoundException * - when properties file was not found */ public int createConnection(InetAddress address, int pingPeriod) throws FileNotFoundException, IOException { Connection connection = new Connection(messenger, 0); connection.connect(address, pingPeriod); synchronized (connections) { connections.add(connection); return connections.size() - 1; } } /** * Creates and initiates {@link Connection} to the remote host with the * default ping frequency. * * @param address * - {@link InetAddress} of the remote host * @return index of the connection * @throws IOException * when properties file was not found * @throws FileNotFoundException * when properties file was not found */ public int createConnection(InetAddress address) throws FileNotFoundException, IOException { synchronized (connections) { Connection connection = new Connection(messenger, connections.size()); connection.connect(address, pingPeriod); connections.add(connection); return connections.size() - 1; } } /** * Gets from the managed system supported {@link CipherSuite}s. Should be * performed only immediately after {@link #createConnection}. * * @param connection * - index of the connection to get available Cipher Suites from * * @return list of the {@link CipherSuite}s supported by the managed system. * @throws ConnectionException * when connection is in the state that does not allow to * perform this operation. * @throws Exception * when sending message to the managed system fails */ public List getAvailableCipherSuites(int connection) throws Exception { int tag = generateSessionlessTag(); List suites; try { suites = connections.get(connection).getAvailableCipherSuites(tag); } catch (Exception e) { freeTag(tag); throw e; } freeTag(tag); return suites; } /** * The method getAvailableCipherSuites fails on some IPMI, because it's an anonymous request and * some IPMI interfaces don't like that. * * So getAllCipherSuites simulate a request (and keep the state machine happy) * but returns all cipher, without requesting the IPMI. * * * @param connection * - index of the connection to get available Cipher Suites from * * @return list of the {@link CipherSuite}s supported by the managed system. * @throws ConnectionException * when connection is in the state that does not allow to * perform this operation. * @throws Exception * when sending message to the managed system fails */ public List getAllCipherSuites(int connection) throws StateConnectionException { int tag = generateSessionlessTag(); List suites; try { suites = connections.get(connection).getAllCipherSuites(tag); } catch (StateConnectionException e) { freeTag(tag); throw e; } freeTag(tag); return suites; } /** * Queries the managed system for the details of the authentification * process. Must be performed after {@link #getAvailableCipherSuites(int)} * * @param connection * - index of the connection to get Channel Authentication * Capabilities from * @param cipherSuite * - {@link CipherSuite} requested for the session * @param requestedPrivilegeLevel * - {@link PrivilegeLevel} requested for the session * @return {@link GetChannelAuthenticationCapabilitiesResponseData} * @throws ConnectionException * when connection is in the state that does not allow to * perform this operation. * @throws Exception * when sending message to the managed system fails */ public GetChannelAuthenticationCapabilitiesResponseData getChannelAuthenticationCapabilities( int connection, CipherSuite cipherSuite, PrivilegeLevel requestedPrivilegeLevel) throws Exception { int tag = generateSessionlessTag(); GetChannelAuthenticationCapabilitiesResponseData responseData; try { responseData = connections.get(connection) .getChannelAuthenticationCapabilities(tag, cipherSuite, requestedPrivilegeLevel); } catch (Exception e) { freeTag(tag); throw e; } freeTag(tag); return responseData; } /** * Initiates the session with the managed system. Must be performed after * {@link #getChannelAuthenticationCapabilities(int, CipherSuite, PrivilegeLevel)} * * @param connection * - index of the connection that starts the session * @param cipherSuite * - {@link CipherSuite} that will be used during the session * @param privilegeLevel * - requested {@link PrivilegeLevel} - most of the time it will * be {@link PrivilegeLevel#User} * @param username * - the username * @param password * - the password matching the username * @param bmcKey * - the key that should be provided if the two-key * authentication is enabled, null otherwise. * @throws ConnectionException * when connection is in the state that does not allow to * perform this operation. * @throws Exception * when sending message to the managed system or initializing * one of the cipherSuite's algorithms fails */ public void startSession(int connection, CipherSuite cipherSuite, PrivilegeLevel privilegeLevel, String username, String password, byte[] bmcKey) throws Exception { int tag = generateSessionlessTag(); try { connections.get(connection).startSession(tag, cipherSuite, privilegeLevel, username, password, bmcKey); } catch (Exception e) { freeTag(tag); throw e; } freeTag(tag); } /** * Registers the listener so it will receive notifications from connection * * @param connection * - index of the {@link Connection} to listen to * @param listener * - {@link ConnectionListener} to notify */ public void registerListener(int connection, ConnectionListener listener) { connections.get(connection).registerListener(listener); } }