Resetting DRAC4 cards.

I recently donated a whole bunch of Dell server to ISC to help upgrade f.root-servers.net.

 

They all had Dell DRAC4 cards in them and I wanted to reset them to a known config before donating them.

Dell does provide some Linux software to talk to the cards, but is is mostly binary packages that run under RedHat Enterprise server or CentOS. Various folks have managed to make racadm and the other tools work under other distributions, but it is a headache to setup.

 

I needed to wipe and stage 50 servers or so and I wanted to be able to do it in as simple a manner as possiable. I didn't want to have to wipe the drives, install CentOS, wipe the DRAC card, then reinstall with the real OS, so I ended up writing the below code. It will talk to the DRAC card over the internal serial port and should run on any OS (including FreeBSD and netBSD) that run Python.

 

 

#!/bin/python

#

# $Revision:: 16                                           $
# $Date:: 2010-01-25 20:01:49 -0500 (Mon, 25 Jan 2010)     $
# $Author:: wkumari                                        $
# Copyright: Warren Kumari (warren@kumari.net) --  2010
#
"""
This tries to connect to a DRAC4 card in a Dell server and
resets the password, IP, mask and gateway.
It assumes that the DRAC card will show up on /dev/ttyS1
and that you have pySerial installed.
It is neither pretty nor elegant, but I needed to reset the
DRAC card on a bunch ofservers and this just works.
It does no error checking, make completely brick your system,
may cuase early baldness, etc.
Options:
--ip: The IP address to set the DRAC to.
--mask: The netmask (dotted quad).
--gw: The gateway to use.
Example:
./rac_reset.py --ip=192.168.0.12 --mask=255.255.255.0 --gw=192.168.0.1
"""
import getopt
import sys
import time
try:
import serial
except:
print ('Unable to import pySerial module.\n'
'On Ubuntu (and similar) you may be able to fix this with:\n'
'apt-get install python-serial')
sys.exit(-1)
PORT = '/dev/ttyS1'
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
OK_STRING = '\x02\x60\x0A\x00\x00\x00\x00\x00\x00\x00\x96\x03'
CALVIN_MD5 = 'e6e66b8981c1030d5650da159e79539a'
def rac_connect():
s = serial.Serial(PORT, timeout = 10)
if s.isOpen():
print 'Connected to: %s' % s.portstr
else:
print "Unable to open: %s", s.portstr
sys.exit(-1)
return s
def xmit(str):
s=rac_connect()
print 'Going to send: %s' % str
s.write(str)
time.sleep(1)
data = ''
if s.inWaiting:
data = data + s.read(s.inWaiting())
time.sleep(0.5)
while s.inWaiting():
data = data + s.read(s.inWaiting())
time.sleep(0.5)
if data:
if data == OK_STRING:
print 'Success!'
else:
print "Got an error: %s\n" % dump(data, len(data))
else:
print 'Got no reply!'
s.close()
# And give the serial port a bit to settle down.
time.sleep(2)
def dump(src, length):
"""Prints the input in hex notation."""
N=0; result=''
while src:
s,src = src[:length],src[length:]
hexa = ' '.join(["%02X"%ord(x) for x in s])
s = s.translate(FILTER)
result += "%04X   %-*s   %s\n" % (N, length*3, hexa, s)
N+=length
return result
def checksum(str):
"""Takes a string an calculates the RAC checksum.
The RAC checksum seems to be made by adding all
the charaters in the string mod 256 and then
negating that..
Args:
str: A string that we want the checksum for.
Returns: A char to be appended to the str to
make the checksum correct.
"""
a = 0
for char in str:
a = a + ord (char)
a = a % 256
checksum = 256 - a
return chr(checksum)
def make_str(command, cmd_no):
"""Returns a string suitable to be handed to the RAC.
It looks like the DRAC expects a struct with various
bits filled in with, um, something. This takes a
command (like "racdump") and returns it embedded in
the struct.
Args:
command: A string containing a command (e.g: "racdump")
cmd_no : An integer, how many cammands we have run. It
appears that the DRAC *may* uses this so that it can
have multiple outstanding commands. Looks like not
actually used.
Returns:
A string, suitable to be passed to the DRAC socket. Includes
checksum."""
PREFIX = "\2"
PAD = "\0"
SUFFIX = "\3"
length = chr(len(command) + 6)
command_str = ("P" + length + PAD + chr(cmd_no) + \
command + PAD)
str  = (PREFIX + command_str)
str = str + checksum(command_str) + SUFFIX
return str
def usage():
print '%s' % __doc__
if __name__ == "__main__":
try:
opts, args = getopt.getopt(sys.argv[1:], "hi:m:g:v",
["help", "ip=", 'mask=', 'gw='])
except getopt.GetoptError, err:
print 'Option %s' % err
sys.exit(2)
verbose = False
ip = ''
mask = ''
gw = ''
for o, a in opts:
if o == "-v":
verbose = True
elif o in ("-h", "--help"):
usage()
sys.exit()
elif o in ("-i", "--ip"):
ip = a
elif o in ("-m", "--mask"):
mask = a
elif o in ("-g", "--gw"):
gw = a
else:
assert False, "unhandled option"
if not ip or not mask or not gw:
print 'Error: must supply --ip, --mask and --gw!'
sys.exit(-1)
print 'Welcome to the RAC reset utility.'
print 'This will reset the DRAC4 root password to "calvin",'
print 'the IP to %s, the mask to %s and the gateway to %s\n' % (
ip, mask, gw)
#  ip = raw_input('Please enter IP: ')
#  mask = raw_input('Please enter mask: ')
#  gw = raw_input('Please enter gateway: ')
cmd_no = 0
xmit(make_str('setoid -g cfgUserAdmin -o cfgUserAdminPassword -i 1 %s' % CALVIN_MD5, cmd_no))
xmit(make_str('setoid -g cfgLanNetworking -o cfgNicGateway %s' % gw.strip(),cmd_no))
xmit(make_str('setoid -g cfgLanNetworking -o cfgNicNetmask %s' % mask.strip(),cmd_no))
xmit(make_str('setoid -g cfgLanNetworking -o cfgNicIpAddress %s' % ip.strip(), cmd_no))
print 'Done!'

Here is a direct link: rac_reset.py

 

 

Additional information