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

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
  • 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. – Free Consulting Dec 29 '10 at 01:10
  • 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!! –  Dec 29 '10 at 05:40
  • 1
    @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. – Free Consulting Dec 29 '10 at 09:21
  • @user205376 yes 10054 is raised when a client is disconnected because of application crash or connection lost, but this can be handled very easily... –  Dec 29 '10 at 11:01
  • Did you check the SNMP configuration of the router? Are your parameters correct? Is there any firewall taht could block SNMP traffic? –  Dec 29 '10 at 13:35
  • @Dorin Duminica, yes what? How **SNMP** agent host would respond with **TCP** RST? And Indy is full of such, hence the first sentence. – Free Consulting Dec 29 '10 at 13:45

2 Answers2

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;
Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • 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. – Daniel Marschall Dec 29 '10 at 03:02
  • +1 RRUZ is playing a lot with internet stuff from what I've seen on his blog :-) good job!! –  Dec 29 '10 at 07:00
  • @Daniel Marschall, yeah, thankfully SendARP will not send anything but rather return already known MAC – Free Consulting Dec 29 '10 at 09:34
  • 2
    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())? – David Heffernan Dec 29 '10 at 10:45
  • 1
    See JwaIpHlpApi where we have converted all of the functions from this DLL for your convenience. – Remko Dec 29 '10 at 14:36
  • @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 http://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. – RRUZ Dec 29 '10 at 15:12
  • 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) –  Dec 29 '10 at 15:47
  • @RRUZ 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! – David Heffernan Dec 29 '10 at 15:59
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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @David where is the declaration of the `EMacAddressError` exception class? – RRUZ Dec 29 '10 at 15:27
  • @RRUZ EMacAddressError = class(Exception); (or whatever you want) – David Heffernan Dec 29 '10 at 15:55
  • @RRUZ 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. – David Heffernan Dec 29 '10 at 17:07
  • @RRUZ 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. – David Heffernan Dec 29 '10 at 17:17
  • @user205376 Er, what are you on about? And what on earth is ungood?! – David Heffernan Dec 29 '10 at 17:18
  • not going to vote up because you just had me to explain classical Orwell reference :-P Anyway, Rtl function family is a mess. – Free Consulting Dec 29 '10 at 17:34
  • @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! – David Heffernan Dec 29 '10 at 18:06
  • Rtl* functions are semi-private, MS is vacillating where to put them, also Windows 6.0+ – Free Consulting Dec 29 '10 at 19:02
  • @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 . – David Heffernan Dec 29 '10 at 19:10
  • 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.0 – Free Consulting Dec 29 '10 at 19:53
  • @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. – David Heffernan Dec 29 '10 at 20:07
  • Yes, you are writing code for Windows 6.x, Windows 5.x, etc, instead of writing just for Windows – Free Consulting Dec 30 '10 at 05:55