Delphi:获取路由器的MAC地址

8

我使用Delphi,想要确定我的网络中的网络设备的物理MAC地址,特别是路由器本身。

我的代码:

var
  idsnmp: tidsnmp;
  val:string;
begin
  idsnmp := tidsnmp.create;
  try
    idsnmp.QuickSend('.1.3.6.1.2.1.4.22.1.2', 'public', '10.0.0.1', val);
    showmessage(val);
  finally
    idsnmp.free;
  end;
end;

其中10.0.0.1是我的路由器。

不幸的是,QuickSend总是发送“Connection reset by peer #10054”。我试图修改MIB-OID并尝试使用IP 127.0.0.1,该连接应该永远不会失败。我在Google上没有找到任何有用的TIdSNMP教程。:(

祝好 Daniel Marschall


Indy很糟糕,10054与TCP有关。同时,您已经拥有10.0.0.1的ARP记录,请使用IP Helper API进行询问。 - Free Consulting
Indy 其实并不那么糟糕,你需要处理异常...尝试使用 try...except !另外一个我最近发现的事情是,即使你已经处理了异常,有时候仍会出现 #10054 异常。如果你安装了 Eureka Log,可以通过添加 EIdSocket 错误的 EXCEPTION FILTER 来解决这个问题...没有更多的异常,一切都完美地运作了! - user497849
1
@Dorin Duminica: 10054 表示代理主机使用 TCP RST 或 ICMP UNREACH 进行了响应。无论如何,Indy 曾经使用异常进行流程控制,这就是为什么有时会出现一些深度的 Indy 错误泡沫。 - Free Consulting
@user205376 是的,当客户端因应用程序崩溃或连接丢失而断开连接时,会引发10054错误,但这可以非常容易地处理... - user497849
你检查了路由器的SNMP配置吗?你的参数设置正确吗?是否有任何可能会阻塞SNMP流量的防火墙? - user160694
@Dorin Duminica,是的,怎么了?SNMP代理主机如何使用TCP RST响应?Indy充满了这样的内容,因此是第一句话。 - Free Consulting
2个回答

14

你可以使用SendARP函数来获得MAC地址。

查看此示例。

uses
 Windows,
 WinSock,
 SysUtils;


function SendArp(DestIP,SrcIP:ULONG;pMacAddr:pointer;PhyAddrLen:pointer) : DWord; StdCall; external 'iphlpapi.dll' name 'SendARP';


function GetMacAddr(const IPAddress: string; var ErrCode : DWORD): string;
var
MacAddr    : Array[0..5] of Byte;
DestIP     : ULONG;
PhyAddrLen : ULONG;
WSAData    : TWSAData;
begin
  Result    :='';
  WSAStartup($0101, WSAData);
  try
    ZeroMemory(@MacAddr,SizeOf(MacAddr));
    DestIP    :=inet_addr(PAnsiChar(AnsiString(IPAddress)));
    PhyAddrLen:=SizeOf(MacAddr);
    ErrCode   :=SendArp(DestIP,0,@MacAddr,@PhyAddrLen);
    if ErrCode = S_OK then
     Result:=Format('%2.2x-%2.2x-%2.2x-%2.2x-%2.2x-%2.2x',[MacAddr[0], MacAddr[1],MacAddr[2], MacAddr[3], MacAddr[4], MacAddr[5]])
  finally
    WSACleanup;
  end;
end;

非常感谢!你的代码帮了我很多。 :-) 我已经开始阅读 MSDN 上的 IP Helper 参考文档,但我不知道 SendARP() 可以完成这项工作。 - Daniel Marschall
@Daniel Marschall,是的,值得庆幸的是SendARP不会发送任何东西,而是返回已知的MAC。 - Free Consulting
2
我的代码库也使用了同样的方法。请注意,我认为WSAStartup调用是不必要的。我还对在字符串上使用PAnsiChar()感到不信任。在UNICODE Delphi中,它应该是PAnsiChar(AnsiString())吗? - David Heffernan
1
请查看 JwaIpHlpApi,我们已经为您方便地转换了此 DLL 中的所有函数。 - Remko
@RRUZ 很难理解为什么inet_addr需要任何特殊的准备工作 - 它所做的只是将一个字符串分成4个数字并将它们转换为单个字节。实际上,编写自己的inet_addr会更容易,避免将winsock DLL引入到您的进程中! - David Heffernan
显示剩余3条评论

6

不想抢夺 RRUZ 的风头,我提供以下变体,取自我的代码库,并附上一些观察。我将其作为答案而不是评论,以便包含代码。

type
  TMacAddress = array [0..5] of Byte;

function inet_addr(const IPAddress: string): ULONG;
begin
  Result := ULONG(WinSock.inet_addr(PAnsiChar(AnsiString(IPAddress))));
end;

function SendARP(DestIP, SrcIP: ULONG; pMacAddr: Pointer; var PhyAddrLen: ULONG): DWORD; stdcall; external 'Iphlpapi.dll';

function GetMacAddress(const IPAddress: string): TMacAddress;
var
  MaxMacAddrLen: ULONG;
begin
  MaxMacAddrLen := SizeOf(Result);
  if SendARP(inet_addr(IPAddress), 0, @Result, MaxMacAddrLen)<>NO_ERROR then begin
    raise EMacAddressError.CreateFmt('Unable to do SendARP on address: ''%s''', [IPAddress]);
  end;
end;

有几个要点需要说明。

没有必要调用WSAStartup/WSACleanup。

编辑 正如RRUZ在评论中指出的那样,winsock文档并没有明确豁免inet_addr不需要WSAStartup/WSACleanup,因此我撤回这一点。在Vista上,更简单的方法是调用RtlIpv4StringToAddress。话虽如此,inet_addr很容易实现,也许自己写会更容易。

其次,在WinSock.pas中声明inet_addr的方式不正确。它将返回值声明为u_long类型,该类型在WinSock.pas中被定义为Longint。这是一个带符号的4字节整数,但它应该是一个无符号的4字节整数ULONG。如果没有显式转换,可能会出现范围错误。


@David,EMacAddressError异常类的声明在哪里? - RRUZ
不会点赞,因为你刚让我解释了经典的奥威尔引用 :-P 无论如何,Rtl函数族是一团糟。 - Free Consulting
Rtl*函数是半私有的,微软正在犹豫将它们放在哪里,同时适用于Windows 6.0及以上版本。 - Free Consulting
@user205376 好的,我确实提到它们是为Vista+而设计的,但它们在MSDN上有完整的说明,这通常意味着它们会一直存在。至于RtlIpv4StringToAddress,如果您不想被强制加载和初始化WinSock,它明确地提供了一种方法来完成此操作。 - David Heffernan
推动5.x兼容性?从模块化概念向内部函数迁移绝对是一步后退。我最近甚至发现有一个(或者可能两个)Zw*调用仍然以与4.0相同的方式工作。 - Free Consulting
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接