NetBIOS编程获取本机MAC地址及一个小坑

0x00:NetBIOS网络编程

参考资料:NetBIOS及其协议的应用

0x01:获取本机MAC地址

《Windows 网络编程》(第二版)书中 p175 的源代码如下(有清晰注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// GetMacAddress.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#include <Nb30.h>

#pragma comment(lib, "netapi32.lib")

typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff [30];
}ASTAT, * PASTAT;

ASTAT Adapter;

int _tmain(int argc, _TCHAR* argv[])
{
NCB ncb; // NCB结构体,用于设置执行的NetBIOS命令和参数
UCHAR uRetCode; // 执行Netbios()函数的返回值
memset( &ncb, 0, sizeof(ncb) ); // 初始化ncb结构体
ncb.ncb_command = NCBRESET; // 设置执行NCBRESET
ncb.ncb_lana_num = 0; // 设置LANA编号
// 调用Netbios()函数,执行NCBRESET命令
uRetCode = Netbios( &ncb );
// 输出执行NCBRESET命令的结果
printf( "The NCBRESET return code is: 0x%x \n", uRetCode );

memset( &ncb, 0, sizeof(ncb) ); // 初始化ncb
ncb.ncb_command = NCBASTAT; // 执行NCBASTAT命令
ncb.ncb_lana_num = 0; // 设置LANA编号
// 设置执行NCBASTAT命令的参数,将获取到的网络适配器数据保存到Adapter结构体中
memcpy( &ncb.ncb_callname, "* ", 16 );
ncb.ncb_buffer = (UCHAR*) &Adapter;
ncb.ncb_length = sizeof(Adapter);
// 调用Netbios()函数,执行NCBASTAT命令
uRetCode = Netbios( &ncb );
printf( "The NCBASTAT return code is: 0x%x \n", uRetCode );
if ( uRetCode == 0 )
{
// 输出MAC地址
printf( "The Ethernet Number is: %02x-%02x-%02x-%02x-%02x-%02x\n",
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2],
Adapter.adapt.adapter_address[3],
Adapter.adapt.adapter_address[4],
Adapter.adapt.adapter_address[5] );
}
system("pause");
return 0;
}

执行过后的结果如下

显然没有成功获取到MAC地址,再看打印的内容,本来 Netbios() 函数执行过后的返回值应该是0x00,但是结果返回的是0x23,于是在VS中右键Netbios函数 -> 转到定义,上下翻了一下,发现,找到了NETBIOS返回值及含义,如下图

具体所有NETBIOS返回值及含义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
00h : 成功地完成,成功返回
01h : 无效的缓冲区
03h : 无效的命令
05h : 命令超时
06h : 不完整地接收消息
07h : 本地No-Ack命令失败
08h : 无效的本地会话
09h : 没有可使用的资源
0Ah : 会话已关闭
0Bh : 命令已撤消
0Dh : 本地NetBIOS命名表中名字重复
0Eh : NetBIOS命名表满
0Fh : 名字具有活动会话,现被撤消登记
11h : NetBIOS 本地会话表满了
12h : 没有挂起的Listen 命令,所有拒绝断开会话
13h : 非法名字编号
14h : 不能找到被调用名字或无回答
15h : 找不到命令,或不能把*号或00h指定ncb_name的首字节,或名字已被撤消而不能再使用
17h : 名字已被删除18h : 会话非正常结束
19h : 检测到名字冲突
1Ah : 不兼容的远程设备
21h : 接口忙
22h : 挂起的命令太多
23h : 在ncb_lana_num域中无效的编号
24h : 产生取消时,命令已完成
25h : 字节组名命令指定了保留名字
26h : 命令不能被撤消
30h : 被另一个进程定义了名字
34h : NetBIOS环境未被定义
35h : 所用的操作系统资源用尽
36h : 超出最大应用个数
37h : NetBIOS无可以使用的SAP
38h : 无可以使用的请求资源
40h : 系统错误
41h : 检测到远程适配器的热载波
42h : 检测到本地适配器的热载波
43h : 未检测到载波
4Eh : 状态位12、14、或15被置位的时间超过 1 min
4Fh : 状态位8--11中的一个或多个被置位
50h-F6h: 适配器发生故障
F7h : 隐式DIR-INITIALIZE错误
F8h : 隐式DIR-OPEN-ADAPTER 错误
F9h : IBM LAN支持程序内部错误
Fah : 适配器检查
FBh : NetBIOS 程序未被装入PC
FCh : DIR-OPEN-ADAPTER 或 DIR-OPEN-SAP失败
FDh : 不期望关闭适配器
FFh : 命令挂起状态

0x02:脱坑

所以具体原因是出现了在ncb_lana_num域中无效的编号这种错误,即程序中ncb.ncb_lana_num = 0; // 设置LANA编号这句赋值是有问题的,我们需要自己获取本机的网卡信息,如有多少个网卡,每个网卡的编号等,具体代码如下(原来的代码并没有删除,而是注释掉了,方便对比)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// GetMacAddress.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
#include <Nb30.h>
#include <time.h>

#pragma comment(lib, "netapi32.lib")

typedef struct _ASTAT_
{
ADAPTER_STATUS adapt;
NAME_BUFFER NameBuff[30];
}ASTAT, *PASTAT;

ASTAT Adapter;

int _tmain(int argc, _TCHAR* argv[])
{
NCB ncb; // NCB结构体,用于设置执行的NetBIOS命令和参数

LANA_ENUM lana_enum; //包含当前LAN适配器的数量
memset(&lana_enum, 0, sizeof(lana_enum));

UCHAR uRetCode; // 执行Netbios()函数的返回值
memset(&ncb, 0, sizeof(ncb)); // 初始化ncb结构体

ncb.ncb_command = NCBENUM; //统计系统中网卡的数量 指定指令为NCBENUM,用于获取网卡的数量及编号 向网卡发送NCBENUM命令,以获取当前机器的网卡信息,如有多少个网卡,每个网卡的编号(MAC地址)
ncb.ncb_buffer = (unsigned char *)&lana_enum;
ncb.ncb_length = sizeof(LANA_ENUM);
uRetCode = Netbios(&ncb);
if (uRetCode != NRC_GOODRET)
exit(-1);

printf("MAC :\n");
//二、NCBREST:重置LAN适配器
for (int lana = 0; lana<lana_enum.length; lana++)
{
ncb.ncb_command = NCBRESET; //初始化逻辑网卡命令
ncb.ncb_lana_num = lana_enum.lana[lana];
uRetCode = Netbios(&ncb);
memset(&ncb, 0, sizeof(ncb)); // 初始化ncb
ncb.ncb_command = NCBASTAT; // 执行NCBASTAT命令
ncb.ncb_lana_num = lana_enum.lana[lana]; // 设置LANA编号
// 设置执行NCBASTAT命令的参数,将获取到的网络适配器数据保存到Adapter结构体中
strcpy((char*)ncb.ncb_callname, "*");
//memcpy(&ncb.ncb_callname, "* ", 16);
ncb.ncb_buffer = (UCHAR*)&Adapter;
ncb.ncb_length = sizeof(Adapter);
// 调用Netbios()函数,执行NCBASTAT命令
uRetCode = Netbios(&ncb);

//printf("The NCBASTAT return code is: 0x%x \n", uRetCode);
if (uRetCode == 0)
{
// 输出MAC地址
printf(" %02x-%02x-%02x-%02x-%02x-%02x\n",
Adapter.adapt.adapter_address[0],
Adapter.adapt.adapter_address[1],
Adapter.adapt.adapter_address[2],
Adapter.adapt.adapter_address[3],
Adapter.adapt.adapter_address[4],
Adapter.adapt.adapter_address[5]);
}
}
system("pause");
return 0;
}

最终结果如下:

当然,你也可以在代码中加入自己想获取的信息,如打印网卡数量,打印 Netbios() 返回值等。

最后贴两篇我也不知道有没有用的博客:

https://blog.csdn.net/pbl18392021230/article/details/72673417?utm_source=blogxgwz1
https://blog.csdn.net/qq_37711989/article/details/80877905