콘솔상에서 Local IP와 Mac Address를 알아오는 방법은 간단하다.
elenoa# ifconfig
너무나도 간단하게 Local IP와 Mac Address를 알아 올 수 있는데 프로그램 상에서 이것을 얻어오는 방법은 그리 간단하진 않다.
그러니까, 이 포스트는 이 사이트로부터 시작됐다.
참고 사이트 : How to find a machine's local IP address from C
여기에 나와 있는 세가지 예제 중, 가장 첫번째 예제는 ifconfig 결과에서 IP를 parsing 하는 예제로써 별로 참고할만 하지 않다. 두번째 예제는 (아마) 아래와 같(을거)다. (지금 위 사이트가 열리지 않는 관계로 확인하기 힘들다 ㅠ_ㅠ)
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int i;
int s = socket (PF_INET, SOCK_STREAM, 0);
for (i=1;;i++)
{
struct ifreq ifr;
struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
char *ip;
ifr.ifr_ifindex = i;
if (ioctl (s, SIOCGIFNAME, &ifr) < 0)
break;
/* now ifr.ifr_name is set */
if (ioctl (s, SIOCGIFADDR, &ifr) < 0)
continue;
ip = inet_ntoa (sin->sin_addr);
printf ("%s\n", ip);
}
close (s);
return 0;
}
이 예제는 linux에서는 잘 돌아가지만, FreeBSD에서는 돌지 않는다. 'sys/socket.h' 이 include를 추가해줘야 하는 문제 이외에도 SIOCGIFNAME 이 ioctl 옵션이 정의되어 있지 않다. (동일한 기능의 ioctl 옵션이 없는 것 같다.)
세번째 예제는 좀 더 깔끔하게 되어 있는데,
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <ifaddrs.h>
#include <string.h>
int main (int argc, char *argv[])
{
struct ifaddrs *ifa = NULL, *ifp = NULL;
if (getifaddrs (&ifp) < 0)
{
perror ("getifaddrs");
return 1;
}
for (ifa = ifp; ifa; ifa = ifa->ifa_next)
{
char ip[ 200 ];
socklen_t salen;
if (ifa->ifa_addr->sa_family == AF_INET)
salen = sizeof (struct sockaddr_in);
else if (ifa->ifa_addr->sa_family == AF_INET6)
salen = sizeof (struct sockaddr_in6);
else
continue;
if (getnameinfo (ifa->ifa_addr, salen,
ip, sizeof (ip), NULL, 0, NI_NUMERICHOST) < 0)
{
perror ("getnameinfo");
continue;
}
printf ("%s\n", ip);
}
freeifaddrs (ifp);
return 0;
}
이 예제는 getifaddrs 시스템 콜을 이용해서 만들어진 것이다. 아래에 소개할 다른 여러 예제들보단 가장 깔끔하게 만들어지는 것 같다. (getifaddrs 시스템 콜은 BSD 계열에서 시작된 것으로 보이며, 당연히 FreeBSD에서도 깔끔하게 돌아간다.)
다음 예제는 Mailing List에서 가져온 것이다.
참고 사이트 : Re: How to get local IP-Address
/*
* display info about network interfaces
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
#define inaddrr(x) (*(struct in_addr *) &ifr->x[sizeof sa.sin_port])
#define IFRSIZE ((int)(size * sizeof (struct ifreq)))
int main(void)
{
unsigned char *u;
int sockfd, size = 1;
struct ifreq *ifr;
struct ifconf ifc;
struct sockaddr_in sa;
if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) {
fprintf(stderr, "Cannot open socket.\n");
exit(EXIT_FAILURE);
}
ifc.ifc_len = IFRSIZE;
ifc.ifc_req = NULL;
do {
++size;
/* realloc buffer size until no overflow occurs */
if (NULL == (ifc.ifc_req = realloc(ifc.ifc_req, IFRSIZE))) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
ifc.ifc_len = IFRSIZE;
if (ioctl(sockfd, SIOCGIFCONF, &ifc)) {
perror("ioctl SIOCFIFCONF");
exit(EXIT_FAILURE);
}
} while (IFRSIZE <= ifc.ifc_len);
ifr = ifc.ifc_req;
for (;(char *) ifr < (char *) ifc.ifc_req + ifc.ifc_len; ++ifr) {
if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data) {
continue; /* duplicate, skip it */
}
if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) {
continue; /* failed to get flags, skip it */
}
printf("Interface: %s\n", ifr->ifr_name);
printf("IP Address: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
/*
This won't work on HP-UX 10.20 as there's no SIOCGIFHWADDR ioctl. You'll
need to use DLPI or the NETSTAT ioctl on /dev/lan0, etc (and you'll need
to be root to use the NETSTAT ioctl. Also this is deprecated and doesn't
work on 11.00).
On Digital Unix you can use the SIOCRPHYSADDR ioctl according to an old
utility I have. Also on SGI I think you need to use a raw socket, e.g. s
= socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP)
Dave
From: David Peter <dave.peter@eu.citrix.com>
*/
if (0 == ioctl(sockfd, SIOCGIFHWADDR, ifr)) {
/* Select which hardware types to process.
*
* See list in system include file included from
* /usr/include/net/if_arp.h (For example, on
* Linux see file /usr/include/linux/if_arp.h to
* get the list.)
*/
switch (ifr->ifr_hwaddr.sa_family) {
default:
printf("\n");
continue;
case ARPHRD_NETROM: case ARPHRD_ETHER: case ARPHRD_PPP:
case ARPHRD_EETHER: case ARPHRD_IEEE802: break;
}
u = (unsigned char *) &ifr->ifr_addr.sa_data;
if (u[0] + u[1] + u[2] + u[3] + u[4] + u[5]) {
printf("HW Address: %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\n",
u[0], u[1], u[2], u[3], u[4], u[5]);
}
}
if (0 == ioctl(sockfd, SIOCGIFNETMASK, ifr) &&
strcmp("255.255.255.255", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {
printf("Netmask: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
}
if (ifr->ifr_flags & IFF_BROADCAST) {
if (0 == ioctl(sockfd, SIOCGIFBRDADDR, ifr) &&
strcmp("0.0.0.0", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {
printf("Broadcast: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
}
}
if (0 == ioctl(sockfd, SIOCGIFMTU, ifr)) {
printf("MTU: %u\n", ifr->ifr_mtu);
}
if (0 == ioctl(sockfd, SIOCGIFMETRIC, ifr)) {
printf("Metric: %u\n", ifr->ifr_metric);
} printf("\n");
}
close(sockfd);
return EXIT_SUCCESS;
}
조금 길긴 하지만 깔끔하게 출력된다. 이 소스는 FreeBSD에서는 컴파일 되지 않는데, 그러니까 이런 것이다. (아참, 먼저 'netinet/in.h'의 include를 추가해야 한다.)
elenoa-freebsd# gcc test3.c
test3.c: In function `main':
test3.c:78: error: `SIOCGIFHWADDR' undeclared (first use in this function)
test3.c:78: error: (Each undeclared identifier is reported only once
test3.c:78: error: for each function it appears in.)
test3.c:87: error: structure has no member named `ifr_hwaddr'
test3.c:91: error: `ARPHRD_NETROM' undeclared (first use in this function)
test3.c:91: error: `ARPHRD_PPP' undeclared (first use in this function)
test3.c:92: error: `ARPHRD_EETHER' undeclared (first use in this function)
elenoa-freebsd#
그러니까 ioctl에서 SIOCGIFHWADDR 옵션이 없다는 것이고, 이것은 MAC Address를 가져오는 옵션으로, 해당 부분을 삭제해줘야 한다. (아래에 ARPHRD_* 부분도 마찬가지로 삭제.. 이지만 Mac Address를 가져오는 옵션을 삭제해줄 때 같이 삭제된다.) 이 옵션은 아래 참고 사이트 1 에 의하면, SIOCGIFADDR 옵션을 제대로 적용하면 얻을 수 있고, 아니면 sysctl이나 getifaddrs(위에서 했잖아!)를 통해서 얻을 수 있다고 한다. (일단 MAC 주소를 얻는 것이 아니므로 여기서는 패스)
참고 사이트 1 : getting interface MAC addr from C
참고 사이트 2 : [Blue forest-free software] - how to achieve access to the FreeBSD MAC address? Thank you (sysctl을 사용한 예제이다. 확인해보진 않았다. 하지만.. 중국 사이트라니!)
이렇게 수정을 해도 소스는 컴파일이 되지만, FreeBSD에서는 아무 목록도 보이지 않는데, SIOCGIFCONF 접근에 따른 문제가 있는 것 같다. 소스상에서는 버퍼를 작게 잡고 차례차례 늘려나가는데, FreeBSD에서는 처음부터 충분한 버퍼를 가지고 들어가야 하는 것 같다.
또 하나. ifreq structure에서 loop 도는 상황도 조금 다르다. linux에서는 fixed size의 ifreq structure가 여러개 버퍼를 이루는데, FreeBSD에서는 각각의 ifreq structure가 차지하는 크기가 다르다. 여기에 대해서는 아래 사이트의 AIX 샘플에서 아이디어를 얻었다.
참고 사이트 : Pantek Library - Expert Linux and Open Source Services
MAC 주소를 얻는 부분을 제외하고, FreeBSD로 포팅된 해당 샘플은 아래와 같다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define inaddrr(x) (*(struct in_addr *) &ifr->x[sizeof sa.sin_port])
#define IFRSIZE ((int)(size * sizeof (struct ifreq)))
int main(void)
{
unsigned char *u;
int sockfd, size = 100;
struct ifreq *ifr, *ifr_old = NULL, s_ifr;
struct ifconf ifc;
struct sockaddr_in sa;
int i, inc;
if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) {
fprintf(stderr, "Cannot open socket.\n");
exit(EXIT_FAILURE);
}
ifc.ifc_len = IFRSIZE;
ifc.ifc_req = NULL;
/* realloc buffer size until no overflow occurs */
if (NULL == (ifc.ifc_req = realloc(ifc.ifc_req, IFRSIZE))) {
fprintf(stderr, "Out of memory.\n");
exit(EXIT_FAILURE);
}
ifc.ifc_len = IFRSIZE;
if (ioctl(sockfd, SIOCGIFCONF, &ifc)) {
perror("ioctl SIOCFIFCONF");
exit(EXIT_FAILURE);
}
ifr = ifc.ifc_req;
i = ifc.ifc_len;
while (i > 0) {
inc = ifr->ifr_addr.sa_len;
if (inc < sizeof(ifr->ifr_addr))
inc = sizeof(ifr->ifr_addr);
inc += IFNAMSIZ;
if (ifr_old && !strcmp(ifr->ifr_name, ifr_old->ifr_name)) {
goto next_loop; /* duplicate, skip it */
}
if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) {
goto next_loop; /* failed to get flags, skip it */
}
printf("Interface: %s\n", ifr->ifr_name);
if (0 == ioctl(sockfd, SIOCGIFADDR, ifr)) {
printf("IP Address: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
}
/*
This won't work on HP-UX 10.20 as there's no SIOCGIFHWADDR ioctl. You'll
need to use DLPI or the NETSTAT ioctl on /dev/lan0, etc (and you'll need
to be root to use the NETSTAT ioctl. Also this is deprecated and doesn't
work on 11.00).On Digital Unix you can use the SIOCRPHYSADDR ioctl according to an old
utility I have. Also on SGI I think you need to use a raw socket, e.g. s
= socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP)
Dave
From: David Peter <dave.peter@eu.citrix.com>
*/
if (0 == ioctl(sockfd, SIOCGIFNETMASK, ifr) &&
strcmp("255.255.255.255", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {
printf("Netmask: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
}
if (ifr->ifr_flags & IFF_BROADCAST) {
if (0 == ioctl(sockfd, SIOCGIFBRDADDR, ifr) &&
strcmp("0.0.0.0", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {
printf("Broadcast: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
}
}
if (0 == ioctl(sockfd, SIOCGIFMTU, ifr)) {
printf("MTU: %u\n", ifr->ifr_mtu);
}
if (0 == ioctl(sockfd, SIOCGIFMETRIC, ifr)) {
printf("Metric: %u\n", ifr->ifr_metric);
} printf("\n");
next_loop:
ifr_old = ifr;
ifr = (struct ifreq*) (((char*) ifr) + inc);
i -= inc;
}
close(sockfd);
return EXIT_SUCCESS;
}
아참, FreeBSD에서는 SIOCGIFADDR을 호출해줘야 IP Address를 받아온다.
여기까지.
2007/08/16 - [프로그래밍/System (Linux/FreeBSD)] - 서버의 Local IP 가져오기, 단상
'프로그래밍 > System (Linux/FreeBSD)' 카테고리의 다른 글
Linux: Default Router를 가져오는 방법 (0) | 2007.08.15 |
---|---|
FreeBSD: Default Router를 가져오고 변경하는 System Call (0) | 2007.08.14 |
SIGCHLD 핸들러에 wait, waitpid 처리를 해줬는데도 child exit시에 zombie가 생긴다? (0) | 2007.08.02 |
Ramdisk 사용법 (FreeBSD, Linux) (0) | 2007.07.30 |
ps에 나오는 프로세스의 이름 바꾸기 (FreeBSD, linux) (0) | 2007.07.25 |