View Javadoc
1   package org.sentrysoftware.ipmi.core.connection;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * IPMI Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 Verax Systems, Sentry Software
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import org.sentrysoftware.ipmi.core.coding.commands.PrivilegeLevel;
26  import org.sentrysoftware.ipmi.core.coding.commands.session.GetChannelAuthenticationCapabilitiesResponseData;
27  import org.sentrysoftware.ipmi.core.coding.security.CipherSuite;
28  import org.sentrysoftware.ipmi.core.common.PropertiesManager;
29  import org.sentrysoftware.ipmi.core.transport.Messenger;
30  import org.sentrysoftware.ipmi.core.transport.UdpListener;
31  import org.sentrysoftware.ipmi.core.transport.UdpMessenger;
32  
33  import java.io.IOException;
34  import java.net.InetAddress;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.concurrent.atomic.AtomicInteger;
38  
39  /**
40   * Manages multiple {@link Connection}s
41   */
42  public class ConnectionManager {
43      private Messenger messenger;
44      private List<Connection> connections;
45  
46      private static final AtomicInteger sessionlessTag = new AtomicInteger(0);
47      private static List<Integer> reservedTags = new ArrayList<Integer>();
48  
49      /**
50       * Frequency of the no-op commands that will be sent to keep up the session
51       */
52      private long pingPeriod = -1;
53  
54  	/**
55  	 * Initiates the connection manager. Wildcard IP address will be used.
56  	 *
57  	 * @param port       the port at which {@link UdpListener} will work
58  	 * @param pingPeriod frequency of the no-op commands that will be sent to keep
59  	 *                   up the session. 0 to disable ping requests.
60  	 * @throws IOException If UdpMessenger encountered an error
61  	 */
62  	public ConnectionManager(int port, long pingPeriod) throws IOException {
63  		this(port);
64  		this.pingPeriod = pingPeriod;
65  	}
66  
67      /**
68       * Initiates the connection manager. Wildcard IP address will be used.
69       *
70       * @param port
71       *            - the port at which {@link UdpListener} will work
72       * @throws IOException If UdpMessenger encountered an error
73       */
74      public ConnectionManager(int port) throws IOException {
75          messenger = new UdpMessenger(port);
76          initialize();
77      }
78  
79      /**
80       * Initiates the connection manager.
81       *
82       * @param port
83       *            - the port at which {@link UdpListener} will work
84       * @param address
85       *            - the IP interface {@link UdpListener} will bind to
86       * @throws IOException If UdpMessenger encountered an error
87       */
88      public ConnectionManager(int port, InetAddress address) throws IOException {
89          messenger = new UdpMessenger(port, address);
90          initialize();
91      }
92  
93      /**
94       * Initiates the connection manager.
95       *
96       * @param messenger
97       *            - {@link Messenger} to be used in communication
98       */
99      public ConnectionManager(Messenger messenger) {
100         this.messenger = messenger;
101         initialize();
102     }
103 
104     private void initialize() {
105         connections = new ArrayList<Connection>();
106         reservedTags = new ArrayList<Integer>();
107         if (pingPeriod == -1) {
108             pingPeriod = Long.parseLong(PropertiesManager.getInstance().getProperty("pingPeriod"));
109         }
110     }
111 
112     /**
113      * Closes all open connections and disconnects {@link UdpListener}.
114      */
115     public void close() {
116         synchronized (connections) {
117             for (Connection connection : connections) {
118                 if (connection != null && connection.isActive()) {
119                     connection.disconnect();
120                 }
121             }
122         }
123         messenger.closeConnection();
124     }
125 
126     /**
127      * The tag for messages sent outside the session generated by the
128      * {@link ConnectionManager}. Auto-incremented.
129      */
130     public static int generateSessionlessTag() {
131         synchronized (sessionlessTag) {
132             boolean wait = true;
133             while (wait) {
134                 sessionlessTag.incrementAndGet();
135                 sessionlessTag.set(sessionlessTag.get() % 60);
136                 synchronized (reservedTags) {
137                     if (!reservedTags.contains(sessionlessTag.get())) {
138                         wait = false;
139                     }
140                 }
141                 if (wait) {
142                     try {
143                         sessionlessTag.wait(1);
144                     } catch (InterruptedException e) {
145                         // TODO log
146                     }
147                 }
148             }
149             synchronized (reservedTags) {
150                 reservedTags.add(sessionlessTag.get());
151             }
152             return sessionlessTag.get();
153         }
154     }
155 
156     /**
157      * Frees the sessionless tag for further use
158      *
159      * @param tag
160      *            - tag to free
161      */
162     public static void freeTag(int tag) {
163         synchronized (reservedTags) {
164             reservedTags.remove((Integer) tag);
165         }
166     }
167 
168     /**
169      * Returns {@link Connection} identified by index.
170      *
171      * @param index
172      *            - index of the connection to return
173      */
174     public Connection getConnection(int index) {
175         return connections.get(index);
176     }
177 
178     /**
179      * Closes the connection with the given index.
180      */
181     public void closeConnection(int index) {
182         connections.get(index).disconnect();
183     }
184 
185     /**
186      * Returns first {@link Connection} associated with the address
187      *
188      * @param address
189      *            - {@link InetAddress} of the remote host to get connection
190      *            with.
191      * @return First {@link Connection} to the address or null if none found
192      */
193     public Connection getConnection(InetAddress address, int port) {
194         synchronized (connections) {
195             for (Connection connection : connections) {
196                 if (connection != null && connection.isActive()
197                         && connection.getRemoteMachineAddress() == address
198                         && connection.getRemoteMachinePort() == port) {
199                     return connection;
200                 }
201             }
202         }
203         return null;
204     }
205 
206     /**
207      * Creates and initiates {@link Connection} to the remote host.
208      * @param address
209      * - {@link InetAddress} of the remote host
210      * @param pingPeriod
211      * - frequency of the no-op commands that will be sent to keep up the session
212      * @param skipCiphers
213      * - determines if the getAvailableCipherSuites and getChannelAuthenticationCapabilities phases should be skipped
214      * @return index of the connection
215      * @throws IOException
216      * - when properties file was not found
217      */
218     public int createConnection(InetAddress address, int port, int pingPeriod, boolean skipCiphers) throws IOException {
219         Connection connection = new Connection(messenger, 0);
220         connection.connect(address, port, pingPeriod, skipCiphers);
221 
222         synchronized (connections) {
223             connections.add(connection);
224             return connections.size() - 1;
225         }
226     }
227 
228     /**
229      * Creates and initiates {@link Connection} to the remote host.
230      *
231      * @param address
232      *            - {@link InetAddress} of the remote host
233      * @param pingPeriod
234      *            - frequency of the no-op commands that will be sent to keep up
235      *            the session
236      * @return index of the connection
237      * @throws IOException
238      *             - when properties file was not found
239      */
240     public int createConnection(InetAddress address, int port, int pingPeriod) throws IOException {
241         Connection connection = new Connection(messenger, 0);
242         connection.connect(address, port, pingPeriod);
243 
244         synchronized (connections) {
245             connections.add(connection);
246             return connections.size() - 1;
247         }
248     }
249 
250     /**
251      * Creates and initiates {@link Connection} to the remote host with the
252      * default ping frequency.
253      *
254      * @param address
255      *            - {@link InetAddress} of the remote host
256      * @return index of the connection
257      * @throws IOException
258      *             when properties file was not found
259      */
260     public int createConnection(InetAddress address, int port) throws IOException {
261 
262         synchronized (connections) {
263             Connection connection = new Connection(messenger,
264                     connections.size());
265             connection.connect(address, port, pingPeriod);
266             connections.add(connection);
267             return connections.size() - 1;
268         }
269     }
270 
271     /**
272      * Creates and initiates {@link Connection} to the remote host with the default ping frequency.
273      * @param address
274      * - {@link InetAddress} of the remote host
275      * @param skipCiphers
276      * - determines if the getAvailableCipherSuites and getChannelAuthenticationCapabilities phases should be skipped
277      * @return index of the connection
278      * @throws IOException
279      * when properties file was not found
280      */
281     public int createConnection(InetAddress address, int port, boolean skipCiphers) throws IOException {
282         synchronized (connections) {
283             Connection connection = new Connection(messenger, connections.size());
284             connection.connect(address, port, pingPeriod, skipCiphers);
285             connections.add(connection);
286             return connections.size() - 1;
287         }
288     }
289 
290     /**
291      * Gets from the managed system supported {@link CipherSuite}s. Should be
292      * performed only immediately after {@link #createConnection}.
293      *
294      * @param connection
295      *            - index of the connection to get available Cipher Suites from
296      *
297      * @return list of the {@link CipherSuite}s supported by the managed system.
298      * @throws ConnectionException
299      *             when connection is in the state that does not allow to
300      *             perform this operation.
301      * @throws Exception
302      *             when sending message to the managed system fails
303      */
304     public List<CipherSuite> getAvailableCipherSuites(int connection)
305             throws Exception {
306         int tag = generateSessionlessTag();
307         List<CipherSuite> suites;
308         try {
309             suites = connections.get(connection).getAvailableCipherSuites(tag);
310         } catch (Exception e) {
311             freeTag(tag);
312             throw e;
313         }
314         freeTag(tag);
315         return suites;
316     }
317 
318     /**
319      * Queries the managed system for the details of the authentification
320      * process. Must be performed after {@link #getAvailableCipherSuites(int)}
321      *
322      * @param connection
323      *            - index of the connection to get Channel Authentication
324      *            Capabilities from
325      * @param cipherSuite
326      *            - {@link CipherSuite} requested for the session
327      * @param requestedPrivilegeLevel
328      *            - {@link PrivilegeLevel} requested for the session
329      * @return {@link GetChannelAuthenticationCapabilitiesResponseData}
330      * @throws ConnectionException
331      *             when connection is in the state that does not allow to
332      *             perform this operation.
333      * @throws Exception
334      *             when sending message to the managed system fails
335      */
336     public GetChannelAuthenticationCapabilitiesResponseData getChannelAuthenticationCapabilities(
337             int connection, CipherSuite cipherSuite,
338             PrivilegeLevel requestedPrivilegeLevel) throws Exception {
339         int tag = generateSessionlessTag();
340         GetChannelAuthenticationCapabilitiesResponseData responseData;
341         try {
342             responseData = connections.get(connection)
343                     .getChannelAuthenticationCapabilities(tag, cipherSuite,
344                             requestedPrivilegeLevel);
345         } catch (Exception e) {
346             freeTag(tag);
347             throw e;
348         }
349         freeTag(tag);
350         return responseData;
351     }
352 
353     /**
354      * Initiates the session with the managed system. Must be performed after
355      * {@link #getChannelAuthenticationCapabilities(int, CipherSuite, PrivilegeLevel)}
356      *
357      * @param connection
358      *            - index of the connection that starts the session
359      * @param cipherSuite
360      *            - {@link CipherSuite} that will be used during the session
361      * @param privilegeLevel
362      *            - requested {@link PrivilegeLevel} - most of the time it will
363      *            be {@link PrivilegeLevel#User}
364      * @param username
365      *            - the username
366      * @param password
367      *            - the password matching the username
368      * @param bmcKey
369      *            - the key that should be provided if the two-key
370      *            authentication is enabled, null otherwise.
371      * @throws ConnectionException
372      *             when connection is in the state that does not allow to
373      *             perform this operation.
374      * @throws Exception
375      *             when sending message to the managed system or initializing
376      *             one of the cipherSuite's algorithms fails
377      */
378     public int startSession(int connection, CipherSuite cipherSuite,
379             PrivilegeLevel privilegeLevel, String username, String password,
380             byte[] bmcKey) throws Exception {
381         int sessionId;
382         int tag = generateSessionlessTag();
383         try {
384             sessionId = connections.get(connection).startSession(tag, cipherSuite,
385                     privilegeLevel, username, password, bmcKey);
386         } catch (Exception e) {
387             freeTag(tag);
388             throw e;
389         }
390         freeTag(tag);
391 
392         return sessionId;
393     }
394 
395     /**
396      * Registers the listener so it will receive notifications from connection
397      *
398      * @param connection
399      *            - index of the {@link Connection} to listen to
400      * @param listener
401      *            - {@link ConnectionListener} to processResponse
402      */
403     public void registerListener(int connection, ConnectionListener listener) {
404         connections.get(connection).registerListener(listener);
405     }
406 }