All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

Commit 0549709f authored by David Johnson's avatar David Johnson

A simple utility to reset remote nodes over (secure) RMCP/ASF from

Linux/FreeBSD, by talking directly to an ASF-enabled NIC.

The rmcp lib in rmcp.c is quick and dirty.  Since we didn't need IPMI, I
didn't make any real attempt to unentangle RMCP and ASF.  However, if we
do need it, we could easily generalize the code.
parent 57eb3828
#
# EMULAB-COPYRIGHT
# Copyright (c) 2006 University of Utah and the Flux Group.
# All rights reserved.
#
SRCDIR = @srcdir@
TESTBED_SRCDIR = @top_srcdir@
OBJDIR = ../..
SUBDIR = tools/rmanage
include $(OBJDIR)/Makeconf
all: rmanage
include $(TESTBED_SRCDIR)/GNUmakerules
CFLAGS = -Wall
LDFLAGS = -lssl
rmanage: GNUmakefile rmanage.o
$(CC) $(CFLAGS) $(LDFLAGS) rmanage.o -o rmanage
cp rmanage rmanage.debug
strip rmanage
rmanage.o: rmanage.c rmcp.c
$(CC) -c -o rmanage.o $(CFLAGS) $<
rmcp.o: rmcp.c rmcp.h
$(CC) -c -o rmcp.o $(CFLAGS) $<
install boss-install: $(INSTALL_SBINDIR)/rmanage
clean:
rm -f *.o core rmanage rmanage.debug
This directory contains a utility that can use ASF (Alert Standards Format)
over secure (insecure also supported) RMCP (Remote Management and Control
Protocol) to send power control commands and queries to nodes with ASF-enabled
NICs. It does not provide full functionality of a "management console" (i.e.,
does not listen for autonomous heartbeats/alerts from the managed node), but
should still be useful for those wanting to do ASF control from Linux/UNIX.
* Requirements:
- motherboard/chipset support for ASF
- NIC support for ASF (including a utility that can enable ASF on your
NIC and customize the security settings for remote power control)
- Should compile fine under FreeBSD/Linux. Haven't tested on other
unixes, but there shouldn't be (much of) a problem. Will probably not
compile on cygwin/windows.
Files:
* rmanage.c: A simple RMCP client that supports sending ASF commands over
RMCP (also secure RMCP if desired). Note that power cycle/reset/off/on
commands are only supported over secure RMCP. You can also extract
capabilities, supported protocols, and current state, in addition to
sending power control commands (the output of rmanage for these commands is
pretty straightforward). The usage is pasted in below:
Usage: ./rmanage [-hHds] [-tmrkgu <arg>] -c <clientname> <command>
-h Print this message
-d Turn on debugging (more d's mean more debug info)
-s Use secure RMCP
-H Interpret keys as hex strings instead of char strings
-c host The hostname of the managed client
-t timeout Timeout (in seconds) for individual RMCP messages
(default: 3)
-m retries Retry N times for unacknowledged RMCP sends
(default: 3)
-r role Use this role (either 'operator' or 'administrator')
-k key Use this key with the role specified by '-r'
-g key Use this generation key
-u uid Send the specified username
command This argument performs an operation on the managed
client. The available commands are:
ping [Send an RMCP ping and display supported modes.]
capabilities [Get and display RMCP capabilities.]
state [Get and display current node power state.]
nop [Open a session if in secure mode; else nothing.]
reset [Send a warm reset command.]
* rmcp.h: Contains the "API" functions for talking RMCP to a NIC. This is a
pretty quick hack, but it attempts to use a context-based approach, kind of
like some of the Openssl libraries. Kind of. There's a bunch of very
simple high-level functions, but users can get into the guts if they want.
I don't recommend this; the code has not been tested beyond what is
necessary to get rmanage to work on Dell Optiplex 745s with Broadcom
gigabit NICs. It's a decently complete implementation of RMCP/ASF at the
protocol level, anyway.
* rmcp.c: A quick 'n dirty implementation of RMCP/RSP/RAKP/ASF. ASF is
pretty entangled with RMCP at the moment, but it wouldn't be hard to make
it nice and layered so that the library could support IPMI over RMCP, or
whatever...
David Johnson
<johnsond@cs.utah.edu>
December 5, 2006
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2006 University of Utah and the Flux Group.
* All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "rmcp.h"
extern char *optarg;
extern int optopt;
extern int opterr;
extern int optind;
#define COMMAND_PING "ping"
#define COMMAND_CAPABILITIES "capabilities"
#define COMMAND_STATE "state"
#define COMMAND_NOP "nop"
#define COMMAND_RESET "reset"
#define COMMAND_POWER_CYCLE "cycle"
#define COMMAND_POWER_DOWN "off"
#define COMMAND_POWER_UP "on"
#define COMMAND_COUNT 0x08
static char *commands[COMMAND_COUNT][2] =
{
{ COMMAND_PING, "Send an RMCP ping and display supported modes." },
{ COMMAND_CAPABILITIES, "Get and display RMCP capabilities." },
{ COMMAND_STATE, "Get and display current node power state." },
{ COMMAND_NOP, "Open a session if in secure mode; else nothing." },
{ COMMAND_RESET, "Send a warm reset command." },
{ COMMAND_POWER_CYCLE, "Send a full power cycle command." },
{ COMMAND_POWER_DOWN, "Send a power down command." },
{ COMMAND_POWER_UP, "Send a power up command." }
};
#define DEFAULT_TIMEOUT 3
#define DEFAULT_RETRIES 3
void usage(char *prog) {
int i;
fprintf(stderr,
"Usage: %s [-hHds] [-tmrkgu <arg>] -c <clientname> <command>\n"
"\n"
" -h Print this message\n"
" -d Turn on debugging (more d's mean more debug info)\n"
" -s Use secure RMCP\n"
" -H Interpret keys as hex strings instead of char strings\n"
" -c host The hostname of the managed client\n"
" -t timeout Timeout (in seconds) for individual RMCP messages\n"
" (default: %d)\n"
" -m retries Retry N times for unacknowledged RMCP sends\n"
" (default: %d)\n"
" -r role Use this role (either 'operator' or 'administrator')\n"
" -k key Use this key with the role specified by '-r'\n"
" -g key Use this generation key\n"
" -u uid Send the specified username\n"
"\n"
" command This argument performs an operation on the managed\n"
" client. The available commands are:\n",
prog,DEFAULT_TIMEOUT,DEFAULT_RETRIES);
for (i = 0; i < COMMAND_COUNT; ++i) {
fprintf(stderr,
" %s\t[%s]\n",
commands[i][0],commands[i][1]);
}
fprintf(stderr,"\n");
return;
}
int main(int argc,char **argv) {
rmcp_error_t retval;
rmcp_ctx_t *ctx;
rmcp_asf_supported_t *supported;
rmcp_asf_capabilities_t *capabilities;
rmcp_asf_sysstate_t *sysstate;
int c;
int debug = 0;
int secure = 0;
int hex_mode = 0;
char *client = NULL;
u_int8_t *rkey;
int rkey_len;
u_int8_t *gkey;
int gkey_len;
int timeout = DEFAULT_TIMEOUT;
int retries = DEFAULT_RETRIES;
char *role = NULL;
int roleno;
char *uid = NULL;
char *command = NULL;
int i;
while ((c = getopt(argc,argv,"c:t:m:r:k:g:u:hdsH")) != -1) {
switch (c) {
case 'h':
usage(argv[0]);
exit(0);
break;
case 'd':
++debug;
break;
case 's':
secure = 1;
break;
case 'H':
hex_mode = 1;
break;
case 'c':
client = optarg;
break;
case 't':
timeout = atoi(optarg);
break;
case 'm':
retries = atoi(optarg);
break;
case 'r':
role = optarg;
if (strcmp("operator",role) && strcmp("administrator",role)) {
fprintf(stderr,"ERROR: unknown role '%s'!\n",role);
exit(-1);
}
break;
case 'k':
/* rkey = (u_int8_t *)malloc((strlen(optarg)-1)*sizeof(u_int8_t)); */
/* memcpy(rkey,optarg,strlen(optarg)-1); */
/* rkey_len = strlen(optarg)-1; */
rkey = optarg;
rkey_len = strlen(optarg);
break;
case 'g':
gkey = optarg;
gkey_len = strlen(optarg);
/* gkey = (u_int8_t *)malloc((strlen(optarg)-1)*sizeof(u_int8_t)); */
/* memcpy(gkey,optarg,strlen(optarg)-1); */
/* gkey_len = strlen(optarg)-1; */
break;
case 'u':
uid = optarg;
break;
default:
fprintf(stderr,"ERROR: unknown option '%c'!\n",c);
usage(argv[0]);
exit(-1);
break;
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
fprintf(stderr,"ERROR: required command argument is missing!\n");
exit(-1);
}
else {
command = argv[0];
}
if (!secure && (!strcmp(command,"reset") || !strcmp(command,"cycle")
|| !strcmp(command,"on") || !strcmp(command,"off"))) {
fprintf(stderr,"Command '%s' requires secure RMCP.\n",command);
exit(-3);
}
rmcp_set_debug_level(debug);
ctx = rmcp_ctx_init(timeout,retries);
if (secure) {
if (strcmp(role,"administrator") == 0) {
roleno = RMCP_ROLE_ADM;
}
else if (strcmp(role,"operator") == 0) {
roleno = RMCP_ROLE_OP;
}
else {
fprintf(stderr,"Unknown role '%s'!\n",role);
exit(-4);
}
/* rkey_len = gkey_len = 4; */
rmcp_ctx_setsecure(ctx,
roleno,
//rkey,rkey_len, //"1234",4, //rkey_len,
//gkey,gkey_len); //"1234",4); // gkey_len);
"1234",rkey_len,
"1234",gkey_len);
if (uid) {
rmcp_ctx_setuid(ctx,uid,strlen(uid)-1);
}
}
retval = rmcp_open(ctx,client);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Could not open connection to client %s: error 0x%x.\n",
client,retval);
exit(-5);
}
if (secure) {
retval = rmcp_start_secure_session(ctx);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Could not start secure session: error 0x%x\n",
retval);
exit(-6);
}
}
if (!strcmp(command,COMMAND_PING)) {
/* send a ping and print the supported protocols */
retval = rmcp_asf_ping(ctx,&supported);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Command '%s' failed: 0x%x\n",
command,
retval);
exit(-16);
}
rmcp_print_asf_supported(supported,NULL);
}
else if (!strcmp(command,COMMAND_CAPABILITIES)) {
/* get capabilities and print them */
retval = rmcp_asf_get_capabilities(ctx,&capabilities);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Command '%s' failed: 0x%x\n",
command,
retval);
exit(-16);
}
rmcp_print_asf_capabilities(capabilities,NULL);
}
else if (!strcmp(command,COMMAND_STATE)) {
/* get state and print it */
retval = rmcp_asf_get_sysstate(ctx,&sysstate);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Command '%s' failed: 0x%x\n",
command,
retval);
exit(-16);
}
rmcp_print_asf_sysstate(sysstate,NULL);
}
else if (!strcmp(command,COMMAND_NOP)) {
/* do nothing */
fprintf(stdout,
"As requested, nothing done%s.\n",
(secure)?" (except secure session establishment)":"");
}
else if (!strcmp(command,COMMAND_RESET)) {
/* reset */
retval = rmcp_asf_reset(ctx);
if (retval == RMCP_SUCCESS) {
fprintf(stdout,"Reset sent successfully.\n");
}
else {
fprintf(stderr,"Reset unsuccessful: 0x%x\n",retval);
exit(-12);
}
}
else if (!strcmp(command,COMMAND_POWER_CYCLE)) {
/* power cycle */
retval = rmcp_asf_power_cycle(ctx);
if (retval == RMCP_SUCCESS) {
fprintf(stdout,"Power cycle sent successfully.\n");
}
else {
fprintf(stderr,"Power cycle unsuccessful: 0x%x\n",retval);
exit(-12);
}
}
else if (!strcmp(command,COMMAND_POWER_UP)) {
/* power up */
retval = rmcp_asf_power_up(ctx);
if (retval == RMCP_SUCCESS) {
fprintf(stdout,"Power up sent successfully.\n");
}
else {
fprintf(stderr,"Power up unsuccessful: 0x%x\n",retval);
exit(-12);
}
}
else if (!strcmp(command,COMMAND_POWER_DOWN)) {
/* power down */
retval = rmcp_asf_power_down(ctx);
if (retval == RMCP_SUCCESS) {
fprintf(stdout,"Power down sent successfully.\n");
}
else {
fprintf(stderr,"Power down unsuccessful: 0x%x\n",retval);
exit(-12);
}
}
retval = rmcp_finalize(ctx);
if (retval != RMCP_SUCCESS) {
fprintf(stderr,
"Could not close session/connection: 0x%x\n",
retval);
exit(-10);
}
exit(0);
}
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2006 University of Utah and the Flux Group.
* All rights reserved.
*/
#include "rmcp.h"
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <errno.h>
#include <netdb.h>
#include <sys/time.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <time.h>
static int rmcp_debug_level = 0;
#define INFO(level,fmt,...) { \
if (level <= rmcp_debug_level) { \
fputs("INFO: ",stderr); \
fputs(__FUNCTION__,stderr); \
fprintf(stderr,": " fmt "\n", ## __VA_ARGS__); \
} \
}
#define WARN(fmt,...) { \
fputs("WARN: ",stderr); \
fputs(__FUNCTION__,stderr); \
fprintf(stderr,": " fmt "\n", ## __VA_ARGS__); \
}
#define ERROR(fmt,...) { \
fprintf(stderr,"ERROR:%d: ",__LINE__); \
fprintf(stderr,"%s: " fmt "\n", __FUNCTION__, ## __VA_ARGS__); \
}
void rmcp_set_debug_level(int debug) {
rmcp_debug_level = debug;
}
/**
* need a generic protocol read function that reads packets -- and it should
* not need to know anything about protocol state. all packets can be read
* deterministically. we can overlay higher-level protocol functions and
* auto handle acks and dropped packets in higher-level functions -- but they
* can still make use of the packet marshalling functions.
*/
static u_int8_t rmcp_incr_seqno(rmcp_ctx_t *ctx);
static u_int8_t rmcp_rsp_incr_seqno(rmcp_ctx_t *ctx);
static void rmcp_rsp_fill_hdr(rmcp_ctx_t *ctx,rmcp_msg_t *msg);
static void rmcp_fill_hdr(rmcp_ctx_t *ctx,rmcp_msg_t *msg);
/** this function DOES allocate a data segment of requested length and
* copies the data in */
static void rmcp_fill_asf_data(rmcp_ctx_t *ctx,rmcp_msg_t *msg,
u_int8_t type,u_int8_t data_len,u_int8_t *data);
static void rmcp_rsp_fill_tlr(rmcp_ctx_t *ctx,rmcp_msg_t *msg);
static void rmcp_fill_all(rmcp_ctx_t *ctx,rmcp_msg_t *msg,
u_int8_t type,u_int8_t data_len,u_int8_t *data);
static char *rmcp_rsp_rakp_msg_code_str(int code);
rmcp_error_t rmcp_asf_ping(rmcp_ctx_t *ctx,
rmcp_asf_supported_t **supported) {
rmcp_msg_t *send;
rmcp_msg_t *recv;
rmcp_error_t retval;
send = rmcp_build_asf_msg(ctx);
rmcp_fill_asf_ping(ctx,send);
retval = rmcp_send_msg_wait_ack(ctx,send);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(send);
ERROR("error 0x%x",retval);
return retval;
}
else {
rmcp_free_asf_msg(send);
}
retval = rmcp_recv_msg_and_ack(ctx,&recv);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(recv);
ERROR("error 0x%x",retval);
return retval;
}
/* check if the received msg is a presence pong */
/* if not, it's a protocol error! */
if (recv->hdr->class == RMCP_CLASS_ASF && recv->data
&& recv->data->type == RMCP_ASF_TYPE_PRESENCE_PONG) {
*supported = rmcp_decode_asf_supported(recv);
INFO(1,"got supported");
rmcp_free_asf_msg(recv);
return RMCP_SUCCESS;
}
ERROR("protocol error",retval);
rmcp_free_asf_msg(recv);
return RMCP_ERR_PROTOCOL;
}
rmcp_error_t rmcp_asf_get_capabilities(rmcp_ctx_t *ctx,
rmcp_asf_capabilities_t **cap) {
rmcp_msg_t *send;
rmcp_msg_t *recv;
rmcp_error_t retval;
send = rmcp_build_asf_msg(ctx);
rmcp_fill_asf_capabilities_request(ctx,send);
retval = rmcp_send_msg_wait_ack(ctx,send);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(send);
ERROR("error 0x%x",retval);
return retval;
}
else {
rmcp_free_asf_msg(send);
}
retval = rmcp_recv_msg_and_ack(ctx,&recv);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(recv);
ERROR("error 0x%x",retval);
return retval;
}
/* check if the received msg is a presence pong */
/* if not, it's a protocol error! */
if (recv->hdr->class == RMCP_CLASS_ASF && recv->data
&& recv->data->type == RMCP_ASF_TYPE_CAPABILITIES_RESPONSE) {
*cap = rmcp_decode_asf_capabilities(recv);
INFO(1,"got capabilities");
rmcp_free_asf_msg(recv);
return RMCP_SUCCESS;
}
ERROR("protocol error",retval);
rmcp_free_asf_msg(recv);
return RMCP_ERR_PROTOCOL;
}
rmcp_error_t rmcp_asf_get_sysstate(rmcp_ctx_t *ctx,
rmcp_asf_sysstate_t **state) {
rmcp_msg_t *send;
rmcp_msg_t *recv;
rmcp_error_t retval;
send = rmcp_build_asf_msg(ctx);
rmcp_fill_asf_sysstate_request(ctx,send);
retval = rmcp_send_msg_wait_ack(ctx,send);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(send);
ERROR("error 0x%x",retval);
return retval;
}
else {
rmcp_free_asf_msg(send);
}
retval = rmcp_recv_msg_and_ack(ctx,&recv);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(recv);
ERROR("error 0x%x",retval);
return retval;
}
/* check if the received msg is a presence pong */
/* if not, it's a protocol error! */
if (recv->hdr->class == RMCP_CLASS_ASF && recv->data
&& recv->data->type == RMCP_ASF_TYPE_SYSSTATE_RESPONSE) {
*state = rmcp_decode_asf_sysstate(recv);
INFO(1,"got sysstate");
rmcp_free_asf_msg(recv);
return RMCP_SUCCESS;
}
ERROR("protocol error",retval);
rmcp_free_asf_msg(recv);
return RMCP_ERR_PROTOCOL;
}
/**
* XXX: fix reset, up, cycle, to take boot options for inclusion in the data
* field. For now, just set a 0-length data field.
*/
rmcp_error_t rmcp_asf_reset(rmcp_ctx_t *ctx) {
rmcp_msg_t *send;
rmcp_msg_t *recv;
rmcp_error_t retval;
u_int8_t buf[4+1+2+2+2];
int wc;
u_int32_t tmp;
send = rmcp_build_asf_msg(ctx);
/* build the power commands data buf */
wc = 0;
tmp = htonl(RMCP_IANA_ENT_ID);
memcpy(&buf[wc],(u_int8_t *)&tmp,sizeof(u_int32_t));
wc += sizeof(u_int32_t);
/* XXX: no special commands for now! */
memset(&buf[wc],0,7);
wc += 7;
rmcp_fill_all(ctx,send,RMCP_ASF_TYPE_RESET,wc,buf);
retval = rmcp_send_msg_wait_ack(ctx,send);
if (retval != RMCP_SUCCESS) {
rmcp_free_asf_msg(send);
ERROR("error 0x%x",retval);
return retval;
}
else {
rmcp_free_asf_msg(send);