이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다.
저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나
토론을 할 수도 있습니다.
데브피아에서
어떤 분이 질문하셔서 걍 만들어본 예제 입니다. 간단한 ARP에 대한 설명도 추가 했습니다. ARP이 실무에
사용될 가능성은 대단히 낮다고 보지만 필요하신 분들에게 도움이 됐으면 합니다.
ARP (Address
Resolution Protocol) in .NET
ARP (Address Resolution Protocol)은 TCP, UDP, ICMP, GGP 등 IP 프로토콜
패밀리의 하나로서 IP 주소로 부터 MAC 주소(Media Access Control address)를 알아내는 프로토콜을
말한다. 대개 ARP 프로토콜은 TCP/IP를 구현하는 커널 코드에 의해 사용되며 일반 어플리케이션이 사용하지 않는다.
아주 간~~혹~~~ 관리 소프트웨어나 기타 로깅 등의 목적으로 MAC 주소를 사용하기도 하지만 그다지 의미가 없다고 말할
수 있겠다.
이번 포스트에서는 Windows의 IP Helper API에서 제공하는 SendARP 함수를 호출하여 IP 주소를
MAC 주소로 바꾸는 방법에 대해 살펴보도록 하겠다. 그 전에 ARP이 무엇인가에 대해 간략히 노가리를 풀어보겠다.
그리고나서 SendARP를 P/Invoke를 통해 호출하는 interop 코드를 제시하고, 마지막으로 ARP의 유용성에
대해 썰을 풀어보겠다. 요거... 아는 척하기에 좋은 아이템이다... -_-;
IP Address vs. MAC Address
인터넷 프로토콜(IP; Internet Protocol)의 목적 중 하나는 분산되어 있는 로컬 네트워크를 단일
네트워크처럼 묶어주는 것 이였다. 이 때문에 사용된 것이 각 호스트에 가상의 주소를 할당하여 하드웨어 레벨에서 사용되는
네트워크 주소를 추상화시키는 것이다. 눈치 챘겠지만 하드웨어 레벨에서 사용하는 주소가 바로 MAC 주소이며 가상 주소가
바로 IP 주소인 것이다.
NIC (Network Interface Card)가 인식하는 주소는 오로지
MAC 주소일 뿐이다. NIC는 MAC 주소만을 사용하여 네트워크 패킷을 송신하고 수신한다. 우리가 알고 있는
IP 주소는 NIC가 전혀 이해하지 못하는 네트워크 주소일 뿐이다. 그렇다면 TCP 소켓 통신이나 웹 브라우저가 사용하는
HTTP 프로토콜은 어떻게 IP 주소를 MAC 주소로 변경하는 것일까?
ARP Protocol
그 대답이 바로 ARP 프로토콜이다. ARP은 IP 주소를 MAC 주소로
바꾸는데 사용되는 프로토콜이다. ARP의 기본원리는 대단히 간단하다. IP 주소를 가지고 상대방에게
MAC 주소를 물어보는 것이다. 즉, IP 주소를 갖는 대상 호스트에게 너의 MAC
주소는 무엇이냐고 질문을 던지는 것이다. 여기서 의문이 되는 점은 NIC가 MAC 주소만을 이용하여 네트워킹을
한다고 했는데 대상 호스트를 어찌 알아낼까? 간단하다. ARP는 브로드캐스트(Broadcast)를 수행하여 자신의 네트워크
내에 존재하는 모든 호스트에게 물어보는 방식을 취한다. 다시 말해, "이 IP 주소를 가진 녀석은 나에게 너의 MAC
주소를 알려달라"는 식의 ARP 브로드캐스트를 수행한다. 브로드캐스트를 수신한 호스트들은 ARP 패킷내에 존재하는 IP
주소가 자신의 것이라면 자신의 MAC 주소를 ARP을 발신한 호스트에게 알린다. 이렇게 됨으로써 ARP을 발신한 호스트는
쿼리 대상이되는 IP 주소로 부터 MAC 주소를 알아내게 되는 것이다.
ARP이 브로드캐스트를 수행한다는 점에 유의하자. 브로드캐스트는 라우터(게이트웨이)를
통과하지 못한다. 따라서 라우터 바깥 쪽에 존재하는 호스트의 MAC 주소는 ARP를 통해 알아낼 수 없다.
그렇다면 생각해 보자. 내가 웹 브라우저를 통해 구글 사이트에 접속하려고 한다고 가정해 보자. NIC는 네트워킹을 위해
상대방의 MAC 주소를 알아야 한다고 했으므로 구글의 MAC 주소를 알아내야 할까? 허허... 구글에 도달하기 위해서는
라우터를 몇 개 아니 십 수개를 거쳐야 할지도 모르는데?
점점 복잡해 지는듯하지만... 인터넷 프로토콜에서 로컬 네트워크 바깥에 존재하는 호스트와 연결하기 위해서 로컬
호스트(로컬 컴퓨터)가 해야 할 할은 네트워크 패킷을 라우터에게 전송하는 것이 전부이다. 라우터는 IP주소를 살펴보고
자신의 라우팅 테이블에 의거해 적절히 패킷을 전달(forward)하는 역할만을 수행한다. 결론은... 컴퓨터의 NIC는
자신의 네트워크 상에 존재하는 호스트들(라우터를 포함하여)에 대한 MAC 주소만을 알고 있으면 전세계의 모든 호스트에 IP를
통해 연결할 수 있다는 것이다.
요약하자면... NIC가 통신하기 위해 MAC 주소가 필요로 하고, IP 주소는 ARP 브로드캐스트를 통해 MAC
주소로 변환될 수 있다. 로컬 네트워크 바깥의 호스트에 연결하기 위해서는 패킷을 라우터(게이트웨이)로 전송하면 되므로
NIC는 라우터에 대한 MAC 주소를 알아내면 되고 이 역시 ARP을 통해 수행될 수 있는 것이다.
Windows & ARP
Windows 운영체제 역시 ARP을 구현하고 있다. 인터넷 프로토콜을 구현하는 디바이스 드라이버는 ARP에 대한
구현을 완전히 수행하고 있으며 현재 커널 상에서 ARP 프로토콜에 의해 구해진 ARP 테이블을 보고자 한다면 arp.exe
를 수행시키면 된다.
프로그램적인 방법으로 MAC 주소를 알아내는 방법은 없을까? 로컬 컴퓨터에 장착된 NIC의 MAC 주소를 알아내는
것이라면 레지스트리를 뒤지던가 아니면 좀 더 우아하게
WMI를 사용할
수도 있겠지만 다른 컴퓨터의 MAC 주소를 알아내는 것은 만만치 않다. 물론 해당 컴퓨터가 Windows 2000 이상이고
해당 컴퓨터에 적절한 권한이 있다면 WMI를 통해 MAC 주소를 알아낼 수 있다. 혹은 SNMP (Simple
Network Management Protocol) 프로토콜을 통해서도 MAC 주소를 알아낼 수도 있을 것이다. 하지만
해당 하지만 해당 컴퓨터가 WMI나 SNMP를 지원하지 않는 UNIX 컴퓨터이거나 WMI가 설치되지 않은 Windows
95/98/Me 류의 컴퓨터라면? 라우터 바깥에 존재하는 호스트에 대해서는 MAC 주소를 알아낼 수 없다는 제약이 있지만
ARP을 사용하는 것을 고려해 볼 수 있다.
Windows 2000 이상에는
IP Helper 라는 API가 제공된다. Windows 95/98/Me에도 존재하는지는 필자도 모르겠다
(배째... -_-;;). 이 API 세트는 TCP/IP와 관련된 다양한 정보를 제공할 뿐만 아니라 IP를 변경하는 등의
함수도 제공한다. 이들 API 함수 중
SendARP 이란 함수가 있다. 이 함수는 ARP 패킷을 브로드캐스트하여 주어진 IP 주소를 MAC 주소로 바꾸어
반환해 준다. 요 함수를 사용하면 제한적으로나마 IP 주소를 MAC 주소로 바꿀 수 있게 되는 것이다.
SendARP Fucntion Interop
C/C++ 라면 iphlpapi.h 헤더를 include 하고 iphlpapi.lib를 링크함으로써 SendARP
함수를 호출할 수 있을 것이다. MSDN 에는 친절하게 예제 코드까지 나와있다. 하지만 닷넷에서 SendARP 함수를
사용하고자 한다면?
닷넷이 비록 친절하다고는 하나, 모든 Windows API에 상응하는 클래스를 제공하는 것은 아니다. SendARP
함수에 대응되는 클래스의 메쏘드는 적어도 필자가 아는 한 없다(혹시 있다면 필자에게 꼭 연락주기 바란다). 그렇다면 죽으나
사나 SendARP 함수를 호출하도록 P/Invoke 선언을 해야만 할 것이다. SendARP 함수의 선언과 관련된 데이터
구조체인
IPAddr의 선언은 다음과 같다.
typedef struct {
union {
struct {
u_char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct {
u_short s_w1,s_w2;
} S_un_w;
u_long S_addr;
} S_un;
} IPAddr;
DWORD SendARP(IPAddr DestIP, IPAddr SrcIP,
PULONG pMacAddr, PULONG PhyAddrLen);
IPAddr 구조체와 SendARP 함수에 대한 닷넷 Interop 선언만을 제공하면 닷넷 코드에서도 SendARP
함수를 호출할 수 있는 것이다. 다음 ARP 클래스는 SendARP 함수와 IPAddr 구조체에 대한 선언이다. 또한
ARP 클래스는 보다 간편한 호출을 위해 SendARP을 래핑하는 헬퍼 메쏘드로서 GetMacAddress 메쏘드 역시
제공한다.
//
// 주) 이 코드는 어떤 보증도 없으며, 예제로써 제시한 것입니다.
// 이 클래스의 전체 혹은 일부를 무단으로 재배포하거나 타 사이트에
게시하는 것은 저작권 위반입니다. ^_^
//
using System;
using System.Net;
using System.Runtime.InteropServices;
// IP Helper의 ARP(Address
Resolution Protocol) 관련 API 래퍼 클래스.
public sealed class ARP
{
[StructLayout(LayoutKind.Explicit)]
private struct IPAddr
{
[FieldOffset(0)]
public byte s_b1;
[FieldOffset(1)]
public byte s_b2;
[FieldOffset(2)]
public byte s_b3;
[FieldOffset(3)]
public byte s_b4;
[FieldOffset(0)]
public ushort s_w1;
[FieldOffset(2)]
public ushort s_w2;
[FieldOffset(0)]
public uint s_addr;
// IP 주소 문자열 값을 할당한다.
public void Set(string address)
{
IPAddress ip = IPAddress.Parse(address);
Set(ip);
}
// System.Net.IPAddress
객체 값을 할당한다.
public void Set(IPAddress ip)
{
byte[] addrBytes = ip.GetAddressBytes();
s_b1 = addrBytes[0];
s_b2 = addrBytes[1];
s_b3 = addrBytes[2];
s_b4 = addrBytes[3];
}
public static IPAddr Empty = new IPAddr();
}
// SendARP API 선언
[DllImport("iphlpapi.dll", EntryPoint = "SendARP")]
private static extern uint _SendARP(IPAddr DestIP, IPAddr SrcIP,
[In, Out] byte[] pMacAddr, ref int PhyAddrLen);
// ARP을 통해 MAC 주소를 가져온다.
public static byte[] GetMacAddress(string destIP)
{
IPAddr destIPAddr = new IPAddr();
IPAddr srcIPAddr = IPAddr.Empty;
uint result = 0;
byte[] mac = new byte[6];
int len = mac.Length;
destIPAddr.Set(destIP);
result = _SendARP(destIPAddr, srcIPAddr, mac, ref len);
if (result == 0) {
return mac;
}
return null;
// 오류코드를 담은 Exception을 생성하는 것도 좋은
생각이지만... 귀차니즘 땀시... -_-;;
}
}
위 코드를 천천히 살펴보면 그다지 어렵지 않음을 알 수 있을 것이다.
IPAddr 구조체의 union 선언은 FieldOffset 특성(attribute)를 통해 설정해 줄 수 있으므로
복잡하지 않다. 그리고, 문자열로 주어지는 IP 주소를 IPAddr 구조체로 수정하는 Set 메쏘드 역시 닷넷
프레임워크에서 기본으로 제공하는 System.Net.IPAddress 클래스의 Parse 메쏘드를 호출하여 손쉽게 처리할
수 있다. GetMacAddress 헬퍼 메쏘드는 주어진 문자열 IP 주소를 IPAddr 구조체로 바꾸어 IP Helper
API의 SendARP 함수를 호출하는 것으로 끝이다. 결과 MAC 주소는 바이트 배열로서 반환된다.
위에서 주어진 ARP 클래스를 사용하는 예제 코드 역시 대단히(?) 쉽다. ARP 클래스의 static 메쏘드인
GetMacAddress 메쏘드를 호출하면 곧바로 MAC 주소를 담은 바이트 배열을 반환해 줄 것이다. 만약 오류가
발생하거나 MAC 주소를 ARP으로 알아낼 수 없다면 null을 반환한다.
byte[] mac;
mac = ARP.GetMacAddress("192.168.1.1");
if (mac != null) {
Console.Write("MAC = ");
for(int i=0; i < mac.Length; i++) {
Console.Write("{0:x2}", mac[i]);
if (i != 5)
Console.Write('-');
}
Console.WriteLine();
}
else {
Console.WriteLine("ARP으로 MAC
주소를 알아낼 수 없습니다.");
}
Limitation of ARP
ARP을 이용하여 MAC 주소를 알아내는 것은 필자의 생각에는 그다지 유용하지 않아 보인다 (에혀... 내가 이 글을
왜 쓰는지 몰러... 흑흑). ARP은 단지 NIC 수준에서 같은
네트워크(IP 주소 와 넷마스크에 의해 결정됨) 상에서 호스트 혹은 라우터(게이트웨이)의 MAC 주소를 알아내기 위한
제한적인 프로토콜일 뿐이다. 만약 서버에서 클라이언트의 MAC 주소를 알아내기 위해 ARP 을 쓴다면
그것은 상당한 넌센스일 가능성이 높다. 클라이언트가 어떤 라우터도 거치지 않고 서버에 연결되어 있어야만 ARP이 효용을
들어낼 것이기 때문이다. 또한 클라이언트의 MAC 주소를 알아내기 위해 다시 한번 ARP 브로드캐스트를 수행한다는 것 역시
효율성 면에서 영 아니올시다란 얘기이다.
필자의 권고는 WMI나 SNMP와 같은 네트워크 매니지먼트 도구들을
사용하는 것이 보다 우아하며 ARP 자체가 가지는 제약사항을 극복하여 원하는 호스트의 MAC 주소를 알아내는 방법이
될 것이다.
그럼에도 불구하고 Interop 까지 써가면서 SendARP를 알려준 이유가 뭐냐고? 걍... 잼있으니까...
Interop 이 재미있지 않은가? 아니면 말고... -_-;;