Spoofing a hostid on Solaris
From Docupedia
Last Updated: 8/3/2007
Contents |
Overview
Various applications depend on the 'hostid' facility provided by Solaris (to include Nexenta) and may refuse to run on a machine with a different 'hostid' than the one they were originally configured for. This can be inconvenient when you are trying to set up redundant servers or in the case of non-SPARC platforms have replaced enough hardware in the machine that the software-generated 'hostid' is no longer the same.
I will attempt to cover here three methods for spoofing a specific hostid so that your application will think it is on the same machine it was configured for. They are, in order of increasing complexity, hackishness, and likelihood to be non-portable to future versions of Solaris:
- Make a shell script replacement for the command-line tool /bin/hostid
- Replace the gethostid() and sysinfo() library calls with custom routines that returns a given hostid, affecting only one application
- Modify the hostid stored in system memory, affecting all applications on the system
Note: OpenSolaris has seen some discussion concerning providing a 'hostid' key=value pair for zone configurations. This has not been done as of the writing of this article (5/24/2007) but if you're reading this at a much later date it might be worth checking into (see the install-discuss mailing list). The solutions presented here are, in all honesty, hacks.
TODO: Poking /dev/kmem needs to be covered (I haven't actually had to do this since it changes the hostid system wide, not something I want, and thus have no working example... feel free to contribute!).
Make a shell script replacement for the command-line tool /bin/hostid
This one is really, really simple. In fact, for a bourne-compatible shell, it can be summarized as follows:
$ echo "YOUR HOSTID" > hostid $ chmod +x hostid $ typeset -x PATH=.:$PATH $ /path/to/the/executable/in/question
Sadly, it will probably only work for applications written in interpreted languages like sh/csh/perl and so forth, and not even those for any sort of robust code because they will probably use the gethostid() system call instead of the command-line utility.
Replace the gethostid() and sysinfo() library calls with custom routines that returns a given hostid
The C code below can be compiled into a dynamically-linkable library by passing it through your favorite C compiler, similar to the following:
gcc -c -shared -o hostid-spoof.so hostid-spoof.c
After doing that, you can use the LD_PRELOAD environment variable to force it to be loaded after all other libraries have been loaded into memory along with your executable. Since it will be loaded last, the symbol-table entry for "gethostid()" will be left pointing to our code rather than the system library's, and when your application calls "gethostid()" it will hit our routine without ever knowing the difference. Our code will return the 32-bit integer version of an 8-character hexadecimal string you must place in the HOSTID_SPOOFED environment variable. Here's the code:
/*
* hostid-spoof.c
* --------------
* Override the Solaris sysinfo() and gethostid() library calls to return
* a different hostid.
*
* Compile as follows:
* gcc -c -shared -o hostid-spoof.so hostid-spoof.c
*
* Use as follows (bourne-compatible example):
* $ HOSTID_SPOOFED=80f37632
* $ export HOSTID_SPOOFED
* $ LD_PRELOAD=/path/to/hostid-spoof.so /bin/hostid
* 80f37632
*
* Authors:
* Stephen Ayotte <sayotte@ciena.com>, May 24 2007
* - gethostid() implementation
* Tim Stewart <tstewart@ciena.com>, Jun 29 2007
* - sysinfo() implementation
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <sys/systeminfo.h>
int sysinfo(int command, char *buf, long count)
{
int result = 0;
unsigned int hostid = 0;
char *hostidString = NULL;
static int (*real_sysinfo)() = NULL;
/* Call the real sysinfo() and determine if we need to override. */
real_sysinfo = (int (*)(int, char *, long)) dlsym(RTLD_NEXT, "sysinfo");
result = real_sysinfo(command, buf, count);
if (result < 0 || command != SI_HW_SERIAL) return result;
/* Replace returned hostid with our own, if set in the environment. */
hostidString = getenv("HOSTID_SPOOFED");
if (hostidString != NULL)
{
hostid = strtoll(hostidString, (char**)NULL, 16);
snprintf(buf, count, "%u", hostid);
}
return result;
}
long gethostid(void)
{
char* hostidString;
long hostid = 0;
static long (*old_gethostid)() = NULL;
/* Fetch the hex string from the environment. */
hostidString = getenv("HOSTID_SPOOFED");
if(hostidString != NULL)
{
/* Convert the hex string to a 32-bit int
* and return it. */
hostid = strtoll(hostidString, (char**)NULL, 16);
return hostid;
}
/* If it couldn't be found in the environment, try to fall
* back on the real hostid (from the real gethostid()
* call) */
old_gethostid = (long (*)()) dlsym(RTLD_NEXT, "gethostid");
if(old_gethostid == NULL)
{
/* If the real gethostid() call cannot be found,
* return 0. */
return 0;
}
/* If the real gethostid() call was found, return the real
* hostid that it gives us. */
return old_gethostid();
}
To make use of this in a bourne shell you'd type something similar to the following (this line runs the 'hostid' command line tool which will also call our code, a good way to verify things are working as expected):
$ HOSTID_SPOOFED=80f37632 $ export HOSTID_SPOOFED $ LD_PRELOAD=/path/to/hostid-spoof.so /bin/hostid 80f37632
In some cases it may be beneficial to hard-code the hostid into the source code if you don't have much control over the environment that your program uses. In particular, this may be needed for any program that sanitizes its environment upon startup.
Modify the hostid stored in system memory
Under construction, will finish this section later.
See Also
Some links that I found useful while discovering this information:
