How to obtain (almost) unique system identifier in a cross platform way?
Asked Answered
I

4

32

I'm looking for a way to get a number which will almost surely change when running the code on different machines and almost surely stay the same between two runs on the same machine.

If I were doing this as a shell script in Linux, I would use something like this:

{ uname -n ; cat /proc/meminfo | head -n1 ; cat /proc/cpuinfo ; } | md5sum

But I need this in C++ (with boost) and at least on Windows, Linux and Mac.

Iritis answered 31/5, 2013 at 13:44 Comment(7)
There's a very good chance that will not give you consistent results on the same machine; most modern CPUs have dynamic frequency scaling, and /proc/cpuinfo reflects the instantaneous frequency!Broil
Possible duplicatePerlie
This looks like the program will be networked (otherwise an ID sounds useless). In that case, you might be better off if you get a unique ID from the server your program connects to and store it locally for subsequent use.Cacogenics
@Oli Charlesworth: Well, that is just a minor detail, I could always grep only the non-changing fields.Iritis
@Nikos C.: Actually I need the server to know that the application has been copied (including all its files), so this wouldn't work... but it's a good idea anyway :)Iritis
@Iritis It might be as easy as figuring out the path where the executable resides. If that changes since the last time, then you know it has been copied. VMWare Workstation does that, for example, and asks the user "did you copy this or did you move it?" If moved, the ID is retained. If copied, a new ID is created.Cacogenics
What about using the mac address of the local box? You would have to write platform independent code to look it up, but it would only be a few lines.Nuclease
C
62

To generate a mostly unique machine id, you can get a few serial numbers from various pieces of hardware on the system. Most processors will have a CPU serial number, the hard disks each have a number, and each network card will have a unique MAC address.

You can get these and build a fingerprint for the machine. You might want to allow some of these numbers to change before declaring it a new machine. ( e.g. if the 2 out of three are the same, then the machine is the same ). So you can deal somewhat gracefully from having a component upgraded.

I've clipped some code from one of my projects that gets these numbers.

Windows:

#include "machine_id.h"   

#define WIN32_LEAN_AND_MEAN        
#include <windows.h>      
#include <intrin.h>       
#include <iphlpapi.h>     
#ifndef _MSC_VER
#include <cpuid.h>
#else
#include <intrin.h>
#endif

// we just need this for purposes of unique machine id. So any one or two mac's is       
// fine. 
u16 hashMacAddress( PIP_ADAPTER_INFO info )          
{        
   u16 hash = 0;          
   for ( u32 i = 0; i < info->AddressLength; i++ )   
   {     
      hash += ( info->Address[i] << (( i & 1 ) * 8 ));        
   }     
   return hash;           
}        

void getMacHash( u16& mac1, u16& mac2 )              
{        
   IP_ADAPTER_INFO AdapterInfo[32];                  
   DWORD dwBufLen = sizeof( AdapterInfo );           

   DWORD dwStatus = GetAdaptersInfo( AdapterInfo, &dwBufLen );                  
   if ( dwStatus != ERROR_SUCCESS )                  
      return; // no adapters.      

   PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;      
   mac1 = hashMacAddress( pAdapterInfo );            
   if ( pAdapterInfo->Next )       
      mac2 = hashMacAddress( pAdapterInfo->Next );   

   // sort the mac addresses. We don't want to invalidate     
   // both macs if they just change order.           
   if ( mac1 > mac2 )     
   {     
      u16 tmp = mac2;     
      mac2 = mac1;        
      mac1 = tmp;         
   }     
}        

u16 getVolumeHash()       
{        
   DWORD serialNum = 0;   

   // Determine if this volume uses an NTFS file system.      
   GetVolumeInformation( "c:\\", NULL, 0, &serialNum, NULL, NULL, NULL, 0 );    
   u16 hash = (u16)(( serialNum + ( serialNum >> 16 )) & 0xFFFF );              

   return hash;           
}        

u16 getCpuHash()          
{        
   int cpuinfo[4] = { 0, 0, 0, 0 };                  
   __cpuid( cpuinfo, 0 );          
   u16 hash = 0;          
   u16* ptr = (u16*)(&cpuinfo[0]); 
   for ( u32 i = 0; i < 8; i++ )   
      hash += ptr[i];     

   return hash;           
}        

const char* getMachineName()       
{        
   static char computerName[1024]; 
   DWORD size = 1024;     
   GetComputerName( computerName, &size );           
   return &(computerName[0]);      
}

Linux and OsX:

#include <stdio.h>
#include <string.h>
#include <unistd.h>          
#include <errno.h>           
#include <sys/types.h>       
#include <sys/socket.h>      
#include <sys/ioctl.h>  
#include <sys/resource.h>    
#include <sys/utsname.h>       
#include <netdb.h>           
#include <netinet/in.h>      
#include <netinet/in_systm.h>                 
#include <netinet/ip.h>      
#include <netinet/ip_icmp.h> 
#include <assert.h>

#ifdef DARWIN                    
#include <net/if_dl.h>       
#include <ifaddrs.h>         
#include <net/if_types.h>    
#else //!DARWIN              
// #include <linux/if.h>        
// #include <linux/sockios.h>   
#endif //!DARWIN               

const char* getMachineName() 
{ 
   static struct utsname u;  

   if ( uname( &u ) < 0 )    
   {       
      assert(0);             
      return "unknown";      
   }       

   return u.nodename;        
}   


//---------------------------------get MAC addresses ------------------------------------unsigned short-unsigned short----------        
// we just need this for purposes of unique machine id. So any one or two mac's is fine.            
unsigned short hashMacAddress( unsigned char* mac )                 
{ 
   unsigned short hash = 0;             

   for ( unsigned int i = 0; i < 6; i++ )              
   {       
      hash += ( mac[i] << (( i & 1 ) * 8 ));           
   }       
   return hash;              
} 

void getMacHash( unsigned short& mac1, unsigned short& mac2 )       
{ 
   mac1 = 0;                 
   mac2 = 0;                 

#ifdef DARWIN                

   struct ifaddrs* ifaphead; 
   if ( getifaddrs( &ifaphead ) != 0 )        
      return;                

   // iterate over the net interfaces         
   bool foundMac1 = false;   
   struct ifaddrs* ifap;     
   for ( ifap = ifaphead; ifap; ifap = ifap->ifa_next )                  
   {       
      struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifap->ifa_addr;     
      if ( sdl && ( sdl->sdl_family == AF_LINK ) && ( sdl->sdl_type == IFT_ETHER ))                 
      {    
          if ( !foundMac1 )  
          {                  
             foundMac1 = true;                
             mac1 = hashMacAddress( (unsigned char*)(LLADDR(sdl))); //sdl->sdl_data) + sdl->sdl_nlen) );       
          } else {           
             mac2 = hashMacAddress( (unsigned char*)(LLADDR(sdl))); //sdl->sdl_data) + sdl->sdl_nlen) );       
             break;          
          }                  
      }    
   }       

   freeifaddrs( ifaphead );  

#else // !DARWIN             

   int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP );                  
   if ( sock < 0 ) return;   

   // enumerate all IP addresses of the system         
   struct ifconf conf;       
   char ifconfbuf[ 128 * sizeof(struct ifreq)  ];      
   memset( ifconfbuf, 0, sizeof( ifconfbuf ));         
   conf.ifc_buf = ifconfbuf; 
   conf.ifc_len = sizeof( ifconfbuf );        
   if ( ioctl( sock, SIOCGIFCONF, &conf ))    
   {       
      assert(0);             
      return;                
   }       

   // get MAC address        
   bool foundMac1 = false;   
   struct ifreq* ifr;        
   for ( ifr = conf.ifc_req; (char*)ifr < (char*)conf.ifc_req + conf.ifc_len; ifr++ ) 
   {       
      if ( ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data )          
         continue;  // duplicate, skip it     

      if ( ioctl( sock, SIOCGIFFLAGS, ifr ))           
         continue;  // failed to get flags, skip it    
      if ( ioctl( sock, SIOCGIFHWADDR, ifr ) == 0 )    
      {    
         if ( !foundMac1 )   
         { 
            foundMac1 = true;                 
            mac1 = hashMacAddress( (unsigned char*)&(ifr->ifr_addr.sa_data));       
         } else {            
            mac2 = hashMacAddress( (unsigned char*)&(ifr->ifr_addr.sa_data));       
            break;           
         } 
      }    
   }       

   close( sock );            

#endif // !DARWIN            

   // sort the mac addresses. We don't want to invalidate                
   // both macs if they just change order.    
   if ( mac1 > mac2 )        
   {       
      unsigned short tmp = mac2;        
      mac2 = mac1;           
      mac1 = tmp;            
   }       
} 

unsigned short getVolumeHash()          
{ 
   // we don't have a 'volume serial number' like on windows. Lets hash the system name instead.    
   unsigned char* sysname = (unsigned char*)getMachineName();       
   unsigned short hash = 0;             

   for ( unsigned int i = 0; sysname[i]; i++ )         
      hash += ( sysname[i] << (( i & 1 ) * 8 ));       

   return hash;              
} 

#ifdef DARWIN                
 #include <mach-o/arch.h>    
 unsigned short getCpuHash()            
 {         
     const NXArchInfo* info = NXGetLocalArchInfo();    
     unsigned short val = 0;            
     val += (unsigned short)info->cputype;               
     val += (unsigned short)info->cpusubtype;            
     return val;             
 }         

#else // !DARWIN             

 static void getCpuid( unsigned int* p, unsigned int ax )       
 {         
    __asm __volatile         
    (   "movl %%ebx, %%esi\n\t"               
        "cpuid\n\t"          
        "xchgl %%ebx, %%esi" 
        : "=a" (p[0]), "=S" (p[1]),           
          "=c" (p[2]), "=d" (p[3])            
        : "0" (ax)           
    );     
 }         

 unsigned short getCpuHash()            
 {         
    unsigned int cpuinfo[4] = { 0, 0, 0, 0 };          
    getCpuid( cpuinfo, 0 );  
    unsigned short hash = 0;            
    unsigned int* ptr = (&cpuinfo[0]);                 
    for ( unsigned int i = 0; i < 4; i++ )             
       hash += (ptr[i] & 0xFFFF) + ( ptr[i] >> 16 );   

    return hash;             
 }         
#endif // !DARWIN            

int main()
{

  printf("Machine: %s\n", getMachineName());
  printf("CPU: %d\n", getCpuHash());
  printf("Volume: %d\n", getVolumeHash());
  return 0;
}    
Cantlon answered 31/5, 2013 at 14:28 Comment(3)
One good thing about this code is that it takes into account the common case where a user has multiple NICs, each with a different MAC address.Goldberg
... that's a little more complicated than I imagined it :-)Iritis
Well, I can easily change the mac address via terminal (ifconfig eth0 hw ether ...), CPUID is not unique but shared by all processors of the same model, machine name can easily be changed as well. This "uniqueness" can be faked easily.Varien
A
6

I know, the question is bit too old to be answered. But I have on many occasions faced this issue. I like the accept solution, but if you have tried the code then you will know that it has issues.

firstly the CPU id is the product ID- it is not the serial. So if you have same CPU in another Server then it is just not going to work. also the MAC Address can be changed with ease.

If you are only trying to get this done on Linux- you could try like hal services. ie.

hal-get-property --udi /org/freedesktop/Hal/devices/computer --key system.hardware.uuid

But best thing probably to do is if you can enforce root access and if you want to get your hands dirty- is to look at the code for dmidecode. It will allow you to extract UUID of Chasis, Bios, Video and System. You cannot beat that :) and with a few tweaking you can convert it to a class.

Antarctic answered 2/2, 2017 at 6:11 Comment(0)
W
0

Maybe you can generate almost unique id from unique hardware ids - MAC is universally unique, you can also use cpu model

In my opinion you should pick only those things which may not be changed frequently like cpu or LAN/WLAN cards.

Walkling answered 31/5, 2013 at 14:25 Comment(5)
You hint at the problem in your answer: LAN/WLAN cards. Lots of machines have multiple network cards, in particular notebooks that have both wireless and wired cards. Those will each have different MAC addresses. Which one are you going to check? And what if the user disables that one and is using the other one?Goldberg
@claudio: not on windows he doesn't.Cantlon
@cody: get them all and create a hash out of them.Cantlon
@rafael Yeah, I just noticed that the code you posted in your answer does that. That's a good idea, I like it. You just have to make sure that whatever enumeration function you call will still give you disabled NICs. I can't recall if GetAdaptersInfo on Windows does that or not. Lots of Windows functions completely ignore disabled network devices, and that could be a problem for mobile users.Goldberg
@RafaelBaptista exactly, concat multiple id or use multiple ids concurrently should "almost" solve the problem.Walkling
D
0

One quite portable solution would be to use modification time of a current executable. stat function is available on unix and windows, although API is different so you would need to use some IFDEFs.

A binary is unlikely to be deployed at the exactly same time to different machines, so the ids should be unique. The drawback is that the binary update will change the ids.

Durtschi answered 31/5, 2013 at 15:34 Comment(2)
If that was all that was needed, it could generate a guid, either locally - through an OS api, or with a server call to a guid generator.Cantlon
@RafaelBaptista But uuid would not survive a system restart and a server component complicates a solution. But actually random uuid written to a file (if missing) is a very good solution. Very portable and with practically 0 probability of generating a duplicate.Durtschi

© 2022 - 2024 — McMap. All rights reserved.