Oct 122014
 

I’d had this sitting around for awhile and thought I’d take another look at it; in this I simply toss out an IPMI Get Device ID command and see what happens. This is an interesting one; the GUID is a Vendor Specific ID – the specification says that it’s “a unique number per device”, and that “a Device GUID should never change over the lifetime of the device”, which makes it a remarkable thing if true; IP addresses, even MAC addrs, are crummy network IDs. Having a remote one would be a handy thing indeed for auditing, scanning, and the like.

snowflakery

every flake is sacred

Keeping in line with the spec in general, this does not require authentication, and is “highly recommended”, but not mandatory. I think some require auth despite spec, which is what I’d recommend vendors to do, it’s actually a bit of a nasty thing to have a system pigeonholed with a high veracity.

The GUID is a weird one; it’s mostly taken from RFC 4122, and can mean various things (look in the comments below for more on this if you’re really interested.) Sometimes it’s immediately and obviously interesting; for instance, you might see something like this from a BMC:

44454c4c-4b00-1046-8035-c6c04f574c31

The first 4 hex pairs, if decoded, spell out “DELL”, which presumably has something to do with the manufacturer :)

Others might use the IANA manufacturing #’s, but I haven’t delved deeply into analyzing all this.

A quick survey of a couple hundred thousand BMCs on the internet showed that about 90% (186291/207833, or 89.6%) seem to indicate a GUID, although at times its garbage, like all 0’s or F’s.

p.s.Good job for you in the 19/8 block, you know who you are!

#!/usr/bin/env python

#
# Do a Get Device ID (see p250 of the IPMI v 2 spec) on a host
#
# Usage: $0 ip-address
#
# Outputs the target and the GUID in one long string as well as broken
# up (tab sep'd); something that looks like:
#
# 10.0.0.1 373030314d530025903eeba000000000 37303031-4d53-0025-903e-eba000000000
#
# (Usually I'd simply emit the GUID, but I was lazy and this made it
# easier when scripting, just change the print at the bottom if you
# want to change it.)
#
# RFC 4122 specifies four different versions of UUID formats and
# generation algorightms suitable for use for a Device GUID in
# IPMI. These are version 1 (0001b) "time based" - and three
# "name-based" versions: version 3 (0011b) "MD5 hash", version 4
# (0100b) "Pseudo-random", and version 5 "SHA1 hash". The version 1
# format is recommended. However, versions 3, 4, or 5 formats are
# also allowed.
#
# Supposedly this is something like this - which illustrates the
# time-based version... they aren't grouped like that in ipmiutil,
# but who knows, really.
#
# Table 20-10, GUID Format
#
# GUID byte Field MSbyte
# 1 node
# 2 node
# 3 node
# 4 node
# 5 node
# 6 node MSbyte
# 7 clock seq and reserved
# 8 clock seq and reserved MSbyte
# 9 time high and version
# 10 time high and version MSbyte
# 11 time mid
# 12 time mid MSbyte
# 13 time low
# 14 time low
# 15 time low
# 16 time low MSbyte
#
# The RFC shows it as:
#
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | time_low |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | time_mid | time_hi_and_version |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# |clk_seq_hi_res | clk_seq_low | node (0-1) |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | node (2-5) |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#
# Name-based ones can be, among other things, derived from X500,
# ISO OIDs, URLs, or FQDNs (appendix C in RFC 4122.)
#
#
#
# Anyway, back to the fun.
#
# This DOES NOT USE AUTHENTICATION. This is as per spec; the command is
# "highly recommended", but not mandatory. I think some require auth
# despite spec, which is what I'd recommend vendors use :)
#
# The GUID is a Vendor Specific ID - "A unique number per device".
# "A Device GUID should never change over the lifetime of the device",
# which makes it a remarkable thing if true; IP addresses, even MAC addrs,
# are crappy network IDs.
#
# This script simply sends request, tears up response, does a little sanity
# checking, prints out some stuff. Comments are often quotes from
# the IPMI 2.0 spec. The output steals the format from ipmiutil...
# no idea why some bytes are reversed and others aren't, even after
# studying spec. Par for the course.
#

import sys
from socket import *

try:
target = sys.argv[1]
except:
print("usage: %s target" % sys.argv[0])
exit(1)

# in seconds
timeout = 10
timeout = 5
# udp
PORT = 623

#
# parts of the packet below
#

# RMCP class IPMI
rmcp_class = "\x06\x00\xff\x07"
auth_type = "\x00"
session_num = "\x00\x00\x00\x00"
session_id = "\x00\x00\x00\x00"

message_len = "\x07" # bytes of stuff below

target_addr = "\x20"
lun_netfn = "\x18" # LUN & NetFn
header_chksm = "\xc8"
source_addr = "\x81"
source_lun = "\x00"

#
# 08h = get device guid - works unauth'd on SM!
# 37h = get system guid
# 25h = get watchdog timer - works on HP ilo2
# 0ah = get command support
# 2fh = get BMC global enables
#
ipmi_cmd = "\x37"

# checksum fu swiped from jarrod/xcat - http://sourceforge.net/p/xcat/code/HEAD/tree/xcat-core/trunk/xCAT-server/lib/perl/xCAT/IPMI.pm
sum = 0;
for byte in target_addr + lun_netfn + header_chksm + source_addr + source_lun + ipmi_cmd:
sum += ord(byte)
sum = ~sum + 1
checksum = "%s" % chr(sum & 0xff)

# IPMI v1.5 session wrapper
payload = rmcp_class + auth_type + session_num + session_id + message_len + \
target_addr + lun_netfn + header_chksm + source_addr + source_lun + \
ipmi_cmd + checksum

#
# create socket & bind to local port
#

udp = socket(AF_INET, SOCK_DGRAM)
sake = udp.getsockname()
udp.bind(sake)

#
# swap pairs of hex digits
#
def swap(s):
# print('\tbefore ' + s)
s = "".join(reversed([s[i:i+2] for i in range(0, len(s), 2)]))
# print('\tafter ' + s)
return(s)

#
# send packet... or die trying
#
try:
udp.settimeout(timeout)

# if udp.sendto(" ", (target, PORT)) <= 0:
if udp.sendto(payload, (target, PORT)) <= 0:
print("couldn't send packet to %s" % target)

# catch response
data,addr = udp.recvfrom(512)

# print(data)

# skip the header
data = data[21:-1]

guid = data.encode('hex')

#
# in "Wired for Management Baseline", they say:
#
# Field Data Type Octet # Note
# ------ ---------- -------- -----
# time_low unsigned 32 bit integer 0-3 The low field of the timestamp.
# time_mid unsigned 16 bit integer 4-5 The middle field of the timestamp.
# time_hi_and_version unsigned 16 bit integer 6-7 The high field of the timestamp multiplexed with the version number.
# clock_seq_hi_and_reserved unsigned 8 bit integer 8 The high field of the clock sequence multiplexed with the variant.
# clock_seq_low unsigned 8 bit integer 9 The low field of the clock sequence.
# node unsigned 48 bit integer 10-15 The spatially unique node identifier.

#
# IPMI reverses some of these... sure, why not... that makes sense
#

# print('len of data to work with: %s\n' % len(data))

# node = swap(data[0:4].encode('hex'))

node = data[0:4].encode('hex')

# print('nodey ' + node)

clock_seq = swap(data[4:5].encode('hex'))
reserved = swap(data[5:6].encode('hex'))

time_hi = swap(data[6:7].encode('hex'))
version = swap(data[7:8].encode('hex'))

time_mid = data[8:10].encode('hex')

time_low = data[10:].encode('hex')

# one big string & IPMIutil formatting style
print("%s\t%s\t%s-%s%s-%s%s-%s-%s" %
(target, node + clock_seq + reserved + time_hi + version + time_mid + time_low,
node, clock_seq, reserved, time_hi, version, time_mid, time_low))

udp.close()

#
# ... this should also catch parsing stuff in parse_response
#
except Exception, e:
sys.stderr.write("%s - hmmm.... problems in IPMI paradise, tonto: %s, bailin'\n" % (target, e))

Sorry, the comment form is closed at this time.