Delphi: Get MAC of Router
Asked Answered
U

2

8

I am using Delphi and I want to determinate the physical MAC address of a network device in my network, in this case the Router itself.

My code:

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;

where 10.0.0.1 is my router.

Alas, QuickSend does always send "Connection reset by peer #10054". I tried to modify the MIB-OID and I also tried the IP 127.0.0.1 which connection should never fail. I did not find any useable Tutorials about TIdSNMP at Google. :-(

Regards Daniel Marschall

Urinal answered 29/12, 2010 at 0:36 Comment(6)
Indy is so crappy, 10054 is TCP related. Meanwhile, you already have ARP record for 10.0.0.1, use IP Helper API to interrogate.Ceria
Indy is not so crappy actually, you have to handle exceptions... try...except anyone?! another thing that I've recently discovered is that even tho' you handle exceptions, sometimes the #10054 exception is still raised IF you have eureka log installed, you can solve this by adding EXCEPTION FILTER for EIdSocket error or something like that... no more exceptions, everything works perfectly!!Julianejuliann
@Dorin Duminica: 10054 means what agent host repried either with TCP RST or ICMP UNREACH. Anyway, Indy used to employ exceptions for flow control, thats why some nonsense error bubbles for depths of Indy time-to-time.Ceria
@user205376 yes 10054 is raised when a client is disconnected because of application crash or connection lost, but this can be handled very easily...Julianejuliann
Did you check the SNMP configuration of the router? Are your parameters correct? Is there any firewall taht could block SNMP traffic?Roybn
@Dorin Duminica, yes what? How SNMP agent host would respond with TCP RST? And Indy is full of such, hence the first sentence.Ceria
C
14

You can use the SendARP function to get the Mac Address.

check this sample

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;
Clayclaybank answered 29/12, 2010 at 1:38 Comment(8)
Thank you very much! You helped me a lot with that code. :-) I have already started to read the IP Helper Reference from MSDN, but I didn't knew that SendARP() does the job.Urinal
+1 RRUZ is playing a lot with internet stuff from what I've seen on his blog :-) good job!!Julianejuliann
@Daniel Marschall, yeah, thankfully SendARP will not send anything but rather return already known MACCeria
My codebase uses this same approach. Note that I don't think the WSAStartup call is necessary. I'm also mistrustful of the use of PAnsiChar() on a string. In UNICODE Delphi shouldn't it be PAnsiChar(AnsiString())?Append
See JwaIpHlpApi where we have converted all of the functions from this DLL for your convenience.Levins
@David, maybe in this particular case the code works without the use of the WSAStartup and WSACleanup functions, but always is recommendable use these functions when you use the Winsocks library, you can check the MSDN site for the inet_addr function to see an example msdn.microsoft.com/en-us/library/ms738563%28v=vs.85%29.aspx, about the use of PAnsiChar the code not pretend be a UNICODE compatible solution.Clayclaybank
AFAIK this will work only as long as the device is on your subnet. If it is not, you will get the MAC address of the router which will forward the your data. SNMP will return the required information regardless of the subnet (as long as it is configured and you have rights to query data)Roybn
@Clayclaybank It's hard to see why inet_addr would require anything special to prime it - all it does is split a string into 4 numbers and convert them to single bytes. Actually it would just be easier to write your own inet_addr and avoid having to bring the winsock DLL into your process!Append
A
6

Not wishing to steal the thunder of RRUZ, I offer the following variant, taken from my codebase, with some observations. I've done this as an answer rather than a comment in order to include code.

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;

There are a couple of points to make.

There is no need to call WSAStartup/WSACleanup.

EDIT As RRUZ points out in a comment, the winsock documentation does not explictly exempt inet_addr from WSAStartup/WSACleanup so I retract this point. On Vista it is simpler just to call RtlIpv4StringToAddress. Having said all that, inet_addr is so easy to implement it may just be easier to roll your own.

Secondly the declaration of inet_addr in WinSock.pas is incorrect. It declares the return value to be of a type u_long which is defined in WinSock.pas as Longint. This is a signed 4 byte integer but it should be an unsigned 4 byte integer, ULONG. Without the explicit cast you can get range errors.

Append answered 29/12, 2010 at 14:41 Comment(12)
@David where is the declaration of the EMacAddressError exception class?Clayclaybank
@Clayclaybank EMacAddressError = class(Exception); (or whatever you want)Append
@Clayclaybank I assume you gave me the downvote for lack of WSAStartup. Actually I probably agree with you. It's always best to follow the official documentation. On Vista I think RtlIpv4StringToAddress is the way to go.Append
@Clayclaybank well, I think it was deserved even if it wasn't from you. Always nice to get an explanation with a down vote mind you.Append
@user205376 Er, what are you on about? And what on earth is ungood?!Append
not going to vote up because you just had me to explain classical Orwell reference :-P Anyway, Rtl function family is a mess.Ceria
@user205376 perhaps you could explain why you regard this function as being double plus ungood. I read 1984 this summer but Newspeak was at the back of my mind when I wrote that comment!Append
Rtl* functions are semi-private, MS is vacillating where to put them, also Windows 6.0+Ceria
@user205376 Well, I did mention that they are for Vista+ but they do have a full write up in MSDN so that normally means they are here for good. As for RtlIpv4StringToAddress it is explicitly offered up as the way to do it if you don't want to be compelled to load and initialise WinSock .Append
and shove 5.x compatibility? migrating to internal function is definitely step backward from modular concept. heck, i even figured out recently what one (or maybe two) Zw* calls are still working the same manner as in 4.0Ceria
@user205376 It's not an internal function, it's officially supported as described by MSDN. As for supporting XP, you do that by using fallback techniques. It's very common to switch behaviour at runtime depending on what functions are available. At least, I do it a lot.Append
Yes, you are writing code for Windows 6.x, Windows 5.x, etc, instead of writing just for WindowsCeria

© 2022 - 2024 — McMap. All rights reserved.