1 package org.metricshub.ipmi.core.connection;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import org.metricshub.ipmi.core.coding.PayloadCoder;
26 import org.metricshub.ipmi.core.coding.commands.IpmiVersion;
27 import org.metricshub.ipmi.core.coding.commands.PrivilegeLevel;
28 import org.metricshub.ipmi.core.coding.commands.ResponseData;
29 import org.metricshub.ipmi.core.coding.commands.session.GetChannelAuthenticationCapabilitiesResponseData;
30 import org.metricshub.ipmi.core.coding.commands.session.GetChannelCipherSuitesResponseData;
31 import org.metricshub.ipmi.core.coding.commands.session.OpenSessionResponseData;
32 import org.metricshub.ipmi.core.coding.commands.session.Rakp1ResponseData;
33 import org.metricshub.ipmi.core.coding.commands.session.Rakp3ResponseData;
34 import org.metricshub.ipmi.core.coding.payload.IpmiPayload;
35 import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
36 import org.metricshub.ipmi.core.coding.protocol.PayloadType;
37 import org.metricshub.ipmi.core.coding.security.AuthenticationRakpHmacSha1;
38 import org.metricshub.ipmi.core.coding.security.CipherSuite;
39 import org.metricshub.ipmi.core.coding.security.ConfidentialityAesCbc128;
40 import org.metricshub.ipmi.core.coding.security.IntegrityHmacSha1_96;
41 import org.metricshub.ipmi.core.common.Constants;
42 import org.metricshub.ipmi.core.common.PropertiesManager;
43 import org.metricshub.ipmi.core.common.TypeConverter;
44 import org.metricshub.ipmi.core.sm.MachineObserver;
45 import org.metricshub.ipmi.core.sm.StateMachine;
46 import org.metricshub.ipmi.core.sm.actions.ErrorAction;
47 import org.metricshub.ipmi.core.sm.actions.GetSikAction;
48 import org.metricshub.ipmi.core.sm.actions.MessageAction;
49 import org.metricshub.ipmi.core.sm.actions.ResponseAction;
50 import org.metricshub.ipmi.core.sm.actions.StateMachineAction;
51 import org.metricshub.ipmi.core.sm.events.AuthenticationCapabilitiesReceived;
52 import org.metricshub.ipmi.core.sm.events.Authorize;
53 import org.metricshub.ipmi.core.sm.events.CloseSession;
54 import org.metricshub.ipmi.core.sm.events.Default;
55 import org.metricshub.ipmi.core.sm.events.DefaultAck;
56 import org.metricshub.ipmi.core.sm.events.GetChannelCipherSuitesPending;
57 import org.metricshub.ipmi.core.sm.events.OpenSessionAck;
58 import org.metricshub.ipmi.core.sm.events.Rakp2Ack;
59 import org.metricshub.ipmi.core.sm.events.StartSession;
60 import org.metricshub.ipmi.core.sm.events.Timeout;
61 import org.metricshub.ipmi.core.sm.states.Authcap;
62 import org.metricshub.ipmi.core.sm.states.Ciphers;
63 import org.metricshub.ipmi.core.sm.states.SessionValid;
64 import org.metricshub.ipmi.core.sm.states.Uninitialized;
65 import org.metricshub.ipmi.core.transport.Messenger;
66
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 import java.io.FileNotFoundException;
71 import java.io.IOException;
72 import java.net.InetAddress;
73 import java.util.ArrayList;
74 import java.util.EnumMap;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Timer;
78 import java.util.TimerTask;
79 import java.util.concurrent.atomic.AtomicInteger;
80
81
82
83
84 public class Connection extends TimerTask implements MachineObserver {
85 private static final Logger logger = LoggerFactory.getLogger(Connection.class);
86
87 private static final int DEFAULT_CIPHER_SUITE = 3;
88 private static final int SESSION_SEQUENCE_NUMBER_UPPER_BOUND = Integer.MAX_VALUE / 4;
89 private static final String ILLEGAL_CONNECTION_STATE_MESSAGE = "Illegal connection state: ";
90
91 private List<ConnectionListener> listeners;
92 private StateMachine stateMachine;
93
94
95
96
97 private int timeout = -1;
98 private StateMachineAction lastAction;
99 private int sessionId;
100 private int managedSystemSessionId;
101 private byte[] sik;
102
103 private int handle;
104
105 public int getHandle() {
106 return handle;
107 }
108
109 private Map<PayloadType, MessageHandler> messageHandlers;
110
111 private Timer timer;
112
113 private AtomicInteger currentSessionSequenceNumber;
114
115 public int getTimeout() {
116 return timeout;
117 }
118
119 public void setTimeout(int timeout) {
120 this.timeout = timeout;
121
122 for (MessageHandler messageHandler : messageHandlers.values()) {
123 messageHandler.setTimeout(timeout);
124 }
125 }
126
127
128
129
130
131
132
133
134
135
136 public Connection(Messenger messenger, int handle) {
137 stateMachine = new StateMachine(messenger);
138 this.handle = handle;
139 listeners = new ArrayList<ConnectionListener>();
140 timeout = Integer.parseInt(PropertiesManager.getInstance().getProperty("timeout"));
141 messageHandlers = new EnumMap<PayloadType, MessageHandler>(PayloadType.class);
142 currentSessionSequenceNumber = new AtomicInteger(0);
143 }
144
145
146
147
148
149
150
151
152 public void registerListener(ConnectionListener listener) {
153 listeners.add(listener);
154 }
155
156
157
158
159
160
161
162 public void unregisterListener(ConnectionListener listener) {
163 listeners.remove(listener);
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180 public void connect(InetAddress address, int port, long pingPeriod)
181 throws IOException {
182 connect(address, port, pingPeriod, false);
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public void connect(InetAddress address, int port, long pingPeriod, boolean skipCiphers) throws IOException {
200 MessageHandler ipmiMessageHandler = new IpmiMessageHandler(this, timeout);
201 messageHandlers.put(PayloadType.Ipmi, ipmiMessageHandler);
202
203 MessageHandler solMessageHandler = new SolMessageHandler(this, timeout);
204 messageHandlers.put(PayloadType.Sol, solMessageHandler);
205
206
207
208 if (pingPeriod > 0) {
209 timer = new Timer();
210 timer.schedule(this, pingPeriod, pingPeriod);
211 }
212
213 stateMachine.register(this);
214 if (skipCiphers) {
215 stateMachine.start(address, port);
216 sessionId = SessionManager.generateSessionId();
217 stateMachine.setCurrent(new Authcap());
218 } else {
219 stateMachine.start(address, port);
220 }
221 }
222
223
224
225
226
227
228 public void disconnect() {
229 if (timer != null) {
230 timer.cancel();
231 }
232
233 stateMachine.stop();
234
235 for (MessageHandler messageHandler : messageHandlers.values()) {
236 messageHandler.tearDown();
237 }
238 }
239
240
241
242
243
244
245
246
247
248 public boolean isActive() {
249 return stateMachine.isActive();
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 public List<CipherSuite> getAvailableCipherSuites(int tag) throws Exception {
267
268 if (stateMachine.getCurrent().getClass() != Uninitialized.class) {
269 throw new ConnectionException(ILLEGAL_CONNECTION_STATE_MESSAGE + stateMachine.getCurrent().getClass().getSimpleName());
270 }
271
272 boolean process = true;
273
274 ArrayList<byte[]> rawCipherSuites = new ArrayList<byte[]>();
275
276 while (process) {
277
278 lastAction = null;
279
280 stateMachine.doTransition(new GetChannelCipherSuitesPending(tag));
281
282 waitForResponse();
283
284 ResponseAction action = (ResponseAction) lastAction;
285
286 if (!(action.getIpmiResponseData() instanceof GetChannelCipherSuitesResponseData)) {
287 stateMachine.doTransition(new Timeout());
288 throw new ConnectionException(
289 "Response data not matching Get Channel Cipher Suites command.");
290 }
291
292 GetChannelCipherSuitesResponseData responseData = (GetChannelCipherSuitesResponseData) action
293 .getIpmiResponseData();
294
295 rawCipherSuites.add(responseData.getCipherSuiteData());
296
297 if (responseData.getCipherSuiteData().length < 16) {
298 process = false;
299 }
300 }
301
302 stateMachine.doTransition(new DefaultAck());
303
304 int length = 0;
305
306 for (byte[] partial : rawCipherSuites) {
307 length += partial.length;
308 }
309
310 byte[] csRaw = new byte[length];
311
312 int index = 0;
313
314 for (byte[] partial : rawCipherSuites) {
315 System.arraycopy(partial, 0, csRaw, index, partial.length);
316 index += partial.length;
317 }
318
319 return CipherSuite.getCipherSuites(csRaw);
320 }
321
322 private void waitForResponse() throws Exception {
323 int time = 0;
324
325 while (time < timeout && lastAction == null) {
326 try {
327 Thread.sleep(1);
328 } catch (InterruptedException e) {
329 logger.error(e.getMessage(), e);
330 }
331 ++time;
332 }
333
334 if (lastAction == null) {
335 stateMachine.doTransition(new Timeout());
336 throw new ConnectionException("Command timed out");
337 }
338 if (!(lastAction instanceof ResponseAction || lastAction instanceof GetSikAction)) {
339 if (lastAction instanceof ErrorAction) {
340 throw ((ErrorAction) lastAction).getException();
341 }
342 throw new ConnectionException("Invalid StateMachine response: "
343 + lastAction.getClass().getSimpleName());
344 }
345 }
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364 public GetChannelAuthenticationCapabilitiesResponseData getChannelAuthenticationCapabilities(
365 int tag, CipherSuite cipherSuite,
366 PrivilegeLevel requestedPrivilegeLevel) throws Exception {
367
368 if (stateMachine.getCurrent().getClass() != Ciphers.class) {
369 throw new ConnectionException(ILLEGAL_CONNECTION_STATE_MESSAGE
370 + stateMachine.getCurrent().getClass().getSimpleName());
371 }
372
373 lastAction = null;
374
375 stateMachine.doTransition(new Default(cipherSuite, tag,
376 requestedPrivilegeLevel));
377
378 waitForResponse();
379
380 ResponseAction action = (ResponseAction) lastAction;
381
382 if (!(action.getIpmiResponseData() instanceof GetChannelAuthenticationCapabilitiesResponseData)) {
383 stateMachine.doTransition(new Timeout());
384 throw new ConnectionException(
385 "Response data not matching Get Channel Authentication Capabilities command.");
386 }
387
388 GetChannelAuthenticationCapabilitiesResponseData responseData = (GetChannelAuthenticationCapabilitiesResponseData) action
389 .getIpmiResponseData();
390
391 sessionId = SessionManager.generateSessionId();
392
393 stateMachine.doTransition(new AuthenticationCapabilitiesReceived(
394 sessionId, requestedPrivilegeLevel));
395
396 return responseData;
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 public int startSession(int tag, CipherSuite cipherSuite,
427 PrivilegeLevel privilegeLevel, String username, String password,
428 byte[] bmcKey) throws Exception {
429 if (stateMachine.getCurrent().getClass() != Authcap.class) {
430 throw new ConnectionException(ILLEGAL_CONNECTION_STATE_MESSAGE
431 + stateMachine.getCurrent().getClass().getSimpleName());
432 }
433
434 lastAction = null;
435
436
437 stateMachine.doTransition(new Authorize(cipherSuite, tag,
438 privilegeLevel, sessionId));
439
440 waitForResponse();
441
442 ResponseAction action = (ResponseAction) lastAction;
443
444 lastAction = null;
445
446 if (!(action.getIpmiResponseData() instanceof OpenSessionResponseData)) {
447 stateMachine.doTransition(new Timeout());
448 throw new ConnectionException(
449 "Response data not matching OpenSession response data");
450 }
451
452 managedSystemSessionId = ((OpenSessionResponseData) action
453 .getIpmiResponseData()).getManagedSystemSessionId();
454
455 stateMachine.doTransition(new DefaultAck());
456
457
458 stateMachine.doTransition(new OpenSessionAck(cipherSuite,
459 privilegeLevel, tag, managedSystemSessionId, username,
460 password, bmcKey));
461
462 waitForResponse();
463
464 action = (ResponseAction) lastAction;
465
466 lastAction = null;
467
468 if (!(action.getIpmiResponseData() instanceof Rakp1ResponseData)) {
469 stateMachine.doTransition(new Timeout());
470 throw new ConnectionException(
471 "Response data not matching RAKP Message 2: "
472 + action.getIpmiResponseData().getClass()
473 .getSimpleName());
474 }
475
476 Rakp1ResponseData rakp1ResponseData = (Rakp1ResponseData) action
477 .getIpmiResponseData();
478
479 stateMachine.doTransition(new DefaultAck());
480
481
482 stateMachine.doTransition(new Rakp2Ack(cipherSuite, tag, (byte) 0,
483 managedSystemSessionId, rakp1ResponseData));
484
485 waitForResponse();
486
487 action = (ResponseAction) lastAction;
488
489 if (sik == null) {
490 throw new ConnectionException("Session Integrity Key is null");
491 }
492
493 cipherSuite.initializeAlgorithms(sik);
494
495 lastAction = null;
496
497 if (!(action.getIpmiResponseData() instanceof Rakp3ResponseData)) {
498 stateMachine.doTransition(new Timeout());
499 throw new ConnectionException(
500 "Response data not matching RAKP Message 4");
501 }
502
503 stateMachine.doTransition(new DefaultAck());
504 stateMachine.doTransition(new StartSession(cipherSuite, sessionId));
505
506 return sessionId;
507 }
508
509
510
511
512
513
514
515
516 public void closeSession() throws ConnectionException {
517 if (stateMachine.getCurrent().getClass() != SessionValid.class) {
518 throw new ConnectionException(ILLEGAL_CONNECTION_STATE_MESSAGE
519 + stateMachine.getCurrent().getClass().getSimpleName());
520 }
521
522 stateMachine.doTransition(new CloseSession(managedSystemSessionId,
523 messageHandlers.get(PayloadType.Ipmi).getSequenceNumber(), getNextSessionSequenceNumber()));
524 }
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543 public int sendMessage(PayloadCoder payloadCoder, boolean isOneWay) throws ConnectionException {
544 MessageHandler messageHandler = messageHandlers.get(payloadCoder.getSupportedPayloadType());
545
546 if (messageHandler == null) {
547
548 messageHandler = messageHandlers.get(PayloadType.Ipmi);
549 }
550
551 return messageHandler.sendMessage(payloadCoder, stateMachine, managedSystemSessionId, isOneWay);
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565
566 public int retry(int tag, PayloadType messagePayloadType) throws ConnectionException {
567
568 MessageHandler messageHandler = messageHandlers.containsKey(messagePayloadType) ?
569 messageHandlers.get(messagePayloadType) : messageHandlers.get(PayloadType.Ipmi);
570
571 return messageHandler.retryMessage(tag, stateMachine, managedSystemSessionId);
572 }
573
574 private void handleIncomingMessage(Ipmiv20Message message) {
575 MessageHandler messageHandler = messageHandlers.get(message.getPayloadType());
576 messageHandler.handleIncomingMessage(message);
577 }
578
579 public void notifyResponseListeners(int handle, int tag, ResponseData responseData,
580 Exception exception) {
581 for (ConnectionListener listener : listeners) {
582 if (listener != null) {
583 listener.processResponse(responseData, handle, tag, exception);
584 }
585 }
586 }
587
588 public void notifyRequestListeners(IpmiPayload payload) {
589 for (ConnectionListener listener : listeners) {
590 if (listener != null) {
591 listener.processRequest(payload);
592 }
593 }
594 }
595
596 @Override
597 public void notify(StateMachineAction action) {
598 if (action instanceof GetSikAction) {
599 sik = ((GetSikAction) action).getSik();
600 } else if (!(action instanceof MessageAction)) {
601 lastAction = action;
602 if (action instanceof ErrorAction) {
603 ErrorAction errorAction = (ErrorAction) action;
604 logger.error(errorAction.getException().getMessage(), errorAction.getException());
605 }
606 } else {
607 handleIncomingMessage(((MessageAction) action).getIpmiv20Message());
608 }
609 }
610
611
612
613
614
615 @Override
616 public void run() {
617 int result = -1;
618 while (
619 !Thread.currentThread().isInterrupted()
620 && result <= 0
621 && stateMachine.getCurrent() instanceof SessionValid
622 ) {
623 try {
624
625 result = sendMessage(
626 new org.metricshub.ipmi.core.coding.commands.session.GetChannelAuthenticationCapabilities(
627 IpmiVersion.V20, IpmiVersion.V20,
628 ((SessionValid) stateMachine.getCurrent()).getCipherSuite(), PrivilegeLevel.Callback,
629 TypeConverter.intToByte(0xe)
630 ),
631 false
632 );
633
634 Thread.sleep(1000);
635
636 } catch (Exception e) {
637 logger.error(e.getMessage(), e);
638 }
639 }
640 }
641
642 public InetAddress getRemoteMachineAddress() {
643 return stateMachine.getRemoteMachineAddress();
644 }
645
646 public int getRemoteMachinePort() {
647 return stateMachine.getRemoteMachinePort();
648 }
649
650
651
652
653 public boolean isSessionValid() {
654 return stateMachine.getCurrent() instanceof SessionValid;
655 }
656
657 public int getNextSessionSequenceNumber() {
658 int result = currentSessionSequenceNumber.incrementAndGet() % SESSION_SEQUENCE_NUMBER_UPPER_BOUND;
659
660 if (result == 0) {
661 throw new ArithmeticException("Session sequence number overload. Reset session.");
662 }
663
664 return result;
665 }
666
667
668
669
670 public static CipherSuite getDefaultCipherSuite() {
671
672 return new CipherSuite((byte) DEFAULT_CIPHER_SUITE, new AuthenticationRakpHmacSha1().getCode(),
673 new ConfidentialityAesCbc128().getCode(), new IntegrityHmacSha1_96().getCode());
674
675 }
676
677 }