Index:


Resetting DRAC4 cards to factory default

  • 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::                                              $
    # $Date::                                                  $
    # $Author::                                                $
    # 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