您的位置:首页 > 理论基础 > 计算机网络

获取系统网络MAC地址的三种方法

2008-12-06 22:39 417 查看
目的
篇文章的目的是给出一些得知你MAC地址的简单方法。我会解释这些代码是如何工作的,并给出一些简单的例子来阐述。我假设你已经掌握了下面的概念:

Borland C++Builder
简单的网络概念
一些 Win32 API

方法一 - 用Netbios API
个方法是通过微软的Netbios API来得到你机器的MAC地址。这些API是一组提供比所谓的Winsock更底层的网络支持的命令。通过Netbios来得到地质这种方法的确定就是你必须安装了Netbios(如果你在一个Windows网络上并使用了文件共享,就没有这个问题)。另外,这个方法快速又准确。

Netbios API只包含了一个简称为Netbios的函数。这个函数通过一个网络控制块结构作为参数,来告诉函数需要做什么。这个结构的定义如下:

typedef struct _NCB { 
    UCHAR  ncb_command; 
    UCHAR  ncb_retcode; 
    UCHAR  ncb_lsn; 
    UCHAR  ncb_num; 
    PUCHAR ncb_buffer; 
    WORD   ncb_length; 
    UCHAR  ncb_callname[NCBNAMSZ]; 
    UCHAR  ncb_name[NCBNAMSZ]; 
    UCHAR  ncb_rto; 
    UCHAR  ncb_sto; 
    void (CALLBACK *ncb_post) (struct _NCB *); 
    UCHAR  ncb_lana_num; 
    UCHAR  ncb_cmd_cplt; 
#ifdef _WIN64
    UCHAR  ncb_reserve[18];
#else
    UCHAR  ncb_reserve[10]; 
#endif
    HANDLE ncb_event; 
} NCB, *PNCB;
特别需要注意的是ncb_command成员。就是这个成员来告诉Netbios要做什么。我们将用三条指令来得到MAC地址。这些命令在MSDN中的定义如下:

CommandDescription
NCBENUMWindows NT/2000: Enumerates LAN adapter (LANA) numbers. When this code is specified, the ncb_buffer member points to a buffer to be filled with a LANA_ENUM structure.

NCBENUM is not a standard NetBIOS 3.0 command.
NCBRESETResets a LAN adapter. An adapter must be reset before it can accept any other NCB command that specifies the same number in the ncb_lana_num member.
NCBASTATRetrieves the status of either a local or remote adapter. When this code is specified, the ncb_buffer member points to a buffer to be filled with an ADAPTER_STATUS structure, followed by an array of NAME_BUFFER structures.
这就是得到一个或多个系统MAC地址的步骤:

枚举所有的网络适配器
重启每一个适配器以便得到它的正确信息
查询该适配器来得到MAC地址并将地址填入标准的colon-separated格式(指用冒号分割的格式)
下面的代码简单是这些概念的简单示例。有关Netbios函数的更多信息请参考微软帮助文件或MSDN。

netbios.cpp
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>

using namespace std;
#define bzero(thing,sz) memset(thing,0,sz)

bool GetAdapterInfo(int adapter_num, string &mac_addr)
{
    // 重启网络适配器以使我们能开始对它查询
  NCB Ncb;
  memset(&Ncb, 0, sizeof(Ncb));
  Ncb.ncb_command = NCBRESET;
  Ncb.ncb_lana_num = adapter_num;
  if (Netbios(&Ncb) != NRC_GOODRET) {
    mac_addr = "bad (NCBRESET): ";
    mac_addr += string(Ncb.ncb_retcode);
    return false;
  }

  // 准备得到适配器状态快
  bzero(&Ncb,sizeof(Ncb);
  Ncb.ncb_command = NCBASTAT;
  Ncb.ncb_lana_num = adapter_num;
  strcpy((char *) Ncb.ncb_callname, "*");
  struct ASTAT
  {
    ADAPTER_STATUS adapt;
    NAME_BUFFER NameBuff[30];
  } Adapter;
  bzero(&Adapter,sizeof(Adapter));
  Ncb.ncb_buffer = (unsigned char *)&Adapter;
  Ncb.ncb_length = sizeof(Adapter);

  // 取适配器信息,如果成功按标准colon-delimited格式返回它
  if (Netbios(&Ncb) == 0)
  {
    char acMAC[18];
    sprintf(acMAC, "%02X:%02X:%02X:%02X:%02X:%02X",
            int (Adapter.adapt.adapter_address[0]),
            int (Adapter.adapt.adapter_address[1]),
            int (Adapter.adapt.adapter_address[2]),
            int (Adapter.adapt.adapter_address[3]),
            int (Adapter.adapt.adapter_address[4]),
            int (Adapter.adapt.adapter_address[5]));
    mac_addr = acMAC;
    return true;
  }
  else
  {
    mac_addr = "bad (NCBASTAT): ";
    mac_addr += string(Ncb.ncb_retcode);
    return false;
  }
}

int main()
{
  // 得到适配器列表
  LANA_ENUM AdapterList;
  NCB Ncb;
  memset(&Ncb, 0, sizeof(NCB));
  Ncb.ncb_command = NCBENUM;
  Ncb.ncb_buffer = (unsigned char *)&AdapterList;
  Ncb.ncb_length = sizeof(AdapterList);
  Netbios(&Ncb);

  // 得到所有的本地以太网地址
  string mac_addr;
  for (int i = 0; i < AdapterList.length - 1; ++i)
  {
    if (GetAdapterInfo(AdapterList.lana, mac_addr))
    {
      cout << "Adapter " << int (AdapterList.lana[i]) <<
              "'s MAC is " << mac_addr << endl;
    }
    else
    {
      cerr << "Failed to get MAC address! Do you" << endl;
      cerr << "have the NetBIOS protocol installed?" << endl;
      break;
    }
  }

  return 0;
}

[i]//---------------------------------------------------------------------------


方法二 - COM GUID API
个方法用COM API来创建一个GUID(globably unique identifier,全局唯一标识符)并且从那里得到MAC地址。GUID是用来一般地标识系统中的COM组件或者其他对象。他们通过MAC地址(再加上其他东西)计算出来的,并且在表面上看还将地址保留在GUID中。我说表面上的原因是这还不确定。我提供这个方法作主要是作为一个不要做什么的例子。按这种方法你有可能最终得到MAC地址,但也可能你最后会得到一些随机的十六进制数。

这个方法非常的简单,并不需要太多的解释。我们通过CoCreateGuid创建一个GUID并将最后的6字节存入一个字符串。这应该就是MAC地址,但就像我说的一样,并没有方法可以保证。

uuid.cpp
#include <windows.h>
#include <iostream>
#include <conio.h>

using namespace std;

int main()
{
    cout << "MAC address is: ";

    // 请求COM为我们创建一个UUID。如果本机有一个以太网适配器,UUID的最后
    // 6字节(包含Data4的2-7字节)应该就是本地以太网适配器的MAC地址。
    GUID uuid;
    CoCreateGuid(&uuid);
    // 将地址分割出来
    char mac_addr[18];
    sprintf(mac_addr,"%02X:%02X:%02X:%02X:%02X:%02X",
            uuid.Data4[2],uuid.Data4[3],uuid.Data4[4],
            uuid.Data4[5],uuid.Data4[6],uuid.Data4[7]);
    cout << mac_addr << endl;
    getch();
    return 0;
}
方法三 - 用SNMP扩展API 我要谈的第三种方法是通过使用windows中的SNMP(Simple Network Management Protocol,简单网络管理协议)扩展来得到系统的地址。据个人经验,SNMP很复杂,不过下面的代吗应该可以轻松读懂。基本上这些步骤和使用Netbios时的相同:

得到适配器列表
查询每一个适配器的类型和MAC地址
将实际是NIC的适配器保存

我个人并不太熟悉SNMP,但一如我之前所讲,代码非常的清晰。参考以下的地址可获得更多信息:

SNMP Functions
SNMP Variable Types and Request PDU Types
SNMP Structures

snmp.cpp
#include <snmp.h>
#include <conio.h>
#include <stdio.h>

typedef bool(WINAPI * pSnmpExtensionInit) (
        IN DWORD dwTimeZeroReference,
        OUT HANDLE * hPollForTrapEvent,
        OUT AsnObjectIdentifier * supportedView);

typedef bool(WINAPI * pSnmpExtensionTrap) (
        OUT AsnObjectIdentifier * enterprise,
        OUT AsnInteger * genericTrap,
        OUT AsnInteger * specificTrap,
        OUT AsnTimeticks * timeStamp,
        OUT RFC1157VarBindList * variableBindings);

typedef bool(WINAPI * pSnmpExtensionQuery) (
        IN BYTE requestType,
        IN OUT RFC1157VarBindList * variableBindings,
        OUT AsnInteger * errorStatus,
        OUT AsnInteger * errorIndex);

typedef bool(WINAPI * pSnmpExtensionInitEx) (
        OUT AsnObjectIdentifier * supportedView);

void main()
{
  HINSTANCE m_hInst;
  pSnmpExtensionInit m_Init;
  pSnmpExtensionInitEx m_InitEx;
  pSnmpExtensionQuery m_Query;
  pSnmpExtensionTrap m_Trap;
  HANDLE PollForTrapEvent;
  AsnObjectIdentifier SupportedView;
  UINT OID_ifEntryType[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 3};
  UINT OID_ifEntryNum[] = {1, 3, 6, 1, 2, 1, 2, 1};
  UINT OID_ipMACEntAddr[] = {1, 3, 6, 1, 2, 1, 2, 2, 1, 6};
  AsnObjectIdentifier MIB_ifMACEntAddr =
    { sizeof(OID_ipMACEntAddr)  sizeof(UINT), OID_ipMACEntAddr };
  AsnObjectIdentifier MIB_ifEntryType =
    {sizeof(OID_ifEntryType)  sizeof(UINT), OID_ifEntryType};
  AsnObjectIdentifier MIB_ifEntryNum =
    {sizeof(OID_ifEntryNum)  sizeof(UINT), OID_ifEntryNum};
  RFC1157VarBindList varBindList;
  RFC1157VarBind varBind[2];
  AsnInteger errorStatus;
  AsnInteger errorIndex;
  AsnObjectIdentifier MIB_NULL = {0, 0};
  int ret;
  int dtmp;
  int i = 0, j = 0;
  bool found = false;
  char TempEthernet[13];
  m_Init = NULL;
  m_InitEx = NULL;
  m_Query = NULL;
  m_Trap = NULL;

  /* 加载SNMP动态链接库并得到所需函数的地址 */
  m_hInst = LoadLibrary("inetmib1.dll");
  if (m_hInst < (HINSTANCE) HINSTANCE_ERROR)
  {
    m_hInst = NULL;
    return;
  }
  m_Init =
    (pSnmpExtensionInit) GetProcAddress(m_hInst, "SnmpExtensionInit");
  m_InitEx =
    (pSnmpExtensionInitEx) GetProcAddress(m_hInst,
                                          "SnmpExtensionInitEx");
  m_Query =
    (pSnmpExtensionQuery) GetProcAddress(m_hInst,
                                         "SnmpExtensionQuery");
  m_Trap =
    (pSnmpExtensionTrap) GetProcAddress(m_hInst, "SnmpExtensionTrap");
  m_Init(GetTickCount(), &PollForTrapEvent, &SupportedView);

  /* 初始化m_Query将传回的变量列表 */
  varBindList.list = varBind;
  varBind[0].name = MIB_NULL;
  varBind[1].name = MIB_NULL;

  /* 拷贝OID以在接口表中查找到表目(适配器)数量 */
  varBindList.len = 1;        /* 仅处理一条记录 */
  SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryNum);
  ret =
    m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,
            &errorIndex);
  printf("# of adapters in this system : %in",
       varBind[0].value.asnValue.number);
  varBindList.len = 2;

  /* 拷贝入OID接口类型ifType */
  SNMP_oidcpy(&varBind[0].name, &MIB_ifEntryType);

  /* 拷贝入OID地址ifPhysAddress */
  SNMP_oidcpy(&varBind[1].name, &MIB_ifMACEntAddr);

  do
  {

    /* 提交查询。应答将被填入varBindList。 Submit the query.  Responses will be loaded into varBindList.
       我们可以认为后续调用的执行次数与系统所报告的适配器数目相同。 */
    ret =
      m_Query(ASN_RFC1157_GETNEXTREQUEST, &varBindList, &errorStatus,
              &errorIndex);
    if (!ret)
      ret = 1;
    else
        /* 确认已经返回正确的类型 */
      ret =
          SNMP_oidncmp(&varBind[0].name, &MIB_ifEntryType,
                       MIB_ifEntryType.idLength); if (!ret) {
    j++;
    dtmp = varBind[0].value.asnValue.number;
    printf("Interface #%i type : %in", j, dtmp);

    /* 类型6标识以太网接口 */
    if (dtmp == 6)
    {

      /* 这里确认我们有了一个地址 */
      ret =
          SNMP_oidncmp(&varBind[1].name, &MIB_ifMACEntAddr,
                       MIB_ifMACEntAddr.idLength);
      if ((!ret) && (varBind[1].value.asnValue.address.stream != NULL))
      {
        if((varBind[1].value.asnValue.address.stream[0] == 0x44)
          && (varBind[1].value.asnValue.address.stream[1] == 0x45)
          && (varBind[1].value.asnValue.address.stream[2] == 0x53)
          && (varBind[1].value.asnValue.address.stream[3] == 0x54)
          && (varBind[1].value.asnValue.address.stream[4] == 0x00))
        {
          /* 忽略所有的拨号网络适配器 */
          printf("Interface #%i is a DUN adaptern", j);
          continue;
        }
        if ((varBind[1].value.asnValue.address.stream[0] == 0x00)
            && (varBind[1].value.asnValue.address.stream[1] == 0x00)
            && (varBind[1].value.asnValue.address.stream[2] == 0x00)
            && (varBind[1].value.asnValue.address.stream[3] == 0x00)
            && (varBind[1].value.asnValue.address.stream[4] == 0x00)
            && (varBind[1].value.asnValue.address.stream[5] == 0x00))
        {
          /* 忽略其他网络接口返回的NULL地址 */
          printf("Interface #%i is a NULL addressn", j);
          continue;
        }
        sprintf(TempEthernet, "%02x%02x%02x%02x%02x%02x",
                varBind[1].value.asnValue.address.stream[0],
                varBind[1].value.asnValue.address.stream[1],
                varBind[1].value.asnValue.address.stream[2],
                varBind[1].value.asnValue.address.stream[3],
                varBind[1].value.asnValue.address.stream[4],
                varBind[1].value.asnValue.address.stream[5]);
        printf("MAC Address of interface #%i: %sn", j,
               TempEthernet);}
      }
    }
  } while (!ret);         /* 遇到错误时停止。当我们遍历完所有需被测试的接口后会产生一个错误。 */
  getch();

  FreeLibrary(m_hInst);
  /* 释放邦定 */
  SNMP_FreeVarBind(&varBind[0]);
  SNMP_FreeVarBind(&varBind[1]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: