ioctl 호출 시 file descriptor, command, args 가 필요한데,
일반적인 eth0 장치에 대해서는 세번째 인자로 아래의 자료구조를 사용하며,
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
추가로 무선에 대한 wlan0 에 대해서는 아래의 자료구조를 사용한다.
struct iwreq {
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */
} ifr_ifrn;
/* Data part (defined just above) */
union iwreq_data u;
};
command는 아래의 매크로 값을 사용하는데,
#define SIOCGIFNAME 0x8910 /* get iface name */
#define SIOCSIFLINK 0x8911 /* set iface channel */
#define SIOCGIFCONF 0x8912 /* get iface list */
#define SIOCGIFFLAGS 0x8913 /* get flags */
#define SIOCSIFFLAGS 0x8914 /* set flags */
#define SIOCGIFADDR 0x8915 /* get PA address */
#define SIOCSIFADDR 0x8916 /* set PA address */
#define SIOCGIFDSTADDR 0x8917 /* get remote PA address */
#define SIOCSIFDSTADDR 0x8918 /* set remote PA address */
#define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */
#define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */
#define SIOCGIFNETMASK 0x891b /* get network PA mask */
#define SIOCSIFNETMASK 0x891c /* set network PA mask */
매크로는 이름의 이해는 아래와 같이 해석 하면 편하다.
S는 Socket, IO는 Input/Ouput, C는 Configuration, G는 Get, S는 Set
IF는 InterFace 정도로 해석하면 된다.
아래는 ioctl 예제이다.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h> //if_nameindex(), if_freenameindex()
#include <netinet/ether.h>
#include <sys/ioctl.h>
#include <unistd.h>
int main(void)
{
struct if_nameindex * if_arr, * itf;
struct ifreq ifr;
int sock;
sock = socket(PF_INET, SOCK_STREAM, 0); //dont care, TCP, UDP
if (sock < 0)
{
perror("socket error");
return -1;
}
if_arr = if_nameindex();
if (!if_arr)
{
perror("if_nameindex error");
return -1;
}
for (itf = if_arr; itf->if_index != 0 || itf->if_name != NULL; itf++)
{
memcpy(ifr.ifr_name, itf->if_name, sizeof(ifr.ifr_name)); //necessary "ref dev_get_by_name()"
printf("%d, %s\n", itf->if_index, itf->if_name);
ioctl(sock, SIOCGIFADDR, &ifr);
printf("%s\n", inet_ntoa(*(struct in_addr *)&ifr.ifr_addr.sa_data[2]));
ioctl(sock, SIOCGIFHWADDR, &ifr);
printf("%s\n", ether_ntoa((struct ether_addr *)ifr.ifr_hwaddr.sa_data));
}
if_freenameindex(if_arr);
close(sock);
return 0;
}
if_nameindex()는 man page를 참조하길 바란다.
ioctl 예제 소스에서 언급할 부분은 크게 두 가지로,
첫번째는 소켓을 대상으로 한 ioctl을 사용하기 전에 ifreq 구조체 내 인터페이스 이름을 먼저 설정해 주어야 한다.
net_device 예제에서 소켓에 대한 내부적인 ioctl이 dev_get_by_name()으로 인터페이스 이름을 전달한 것과 같은 맥락이다.
두번째는 주소의 출력에 대한 점 이다.
IP주소는 4Bytes의 big-endian(Network Byte Order)으로 표현되어, 문자열 형태로 출력시에는 inet_ntoa() 쓰이는데,
이 함수는 입력으로 in_addr 구조체를 요구한다. 그래서 이에 맞게 형변환을 시켜 주는데 한 가지 문제가 있다.
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sockaddr 구조체는 위와 같으며, sa_data는 원래 Port 번호와 IP주소를 같이 묶어 저장 하는데 사용이 되며,
이 구조체를 사용하기 편하게 만든게 아래 구조체이다.
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
맨 위는 매크로를 따라가면 sin_family라는 이름의 변수이고,
sin_port는 2Bytes로 표현되는 포트 번호를 담는다.
sin_addr는 4Bytes로 표현되는 IPv4 주소를 담고,
마지막 sin_zero는 사용되지 않는다.
그래서 이렇게 3가지가 총 14Bytes의 sa_data를 표현하게 되므로,
sa_data[2] 부터는 IPv4 주소를 저장한다고 볼 수 있는 것 이다.
ether_ntoa()는 6바이트의 이더넷 주소를 사람이 이해하기 편한 형태로 변환 시켜주는 함수로,
ether_addr 이라는 구조체를 입력으로 받는데 이 구조체는 6Bytes의 배열을 래핑시킨 것이기 때문에 sa_data 전체를 넘기면 된다.
'리눅스 커널 > 네트워크' 카테고리의 다른 글
Raw socket incoming packet capture (0) | 2021.10.24 |
---|---|
Ethernet & ARP Header (0) | 2021.10.24 |
IP & ICMP Header (0) | 2021.10.24 |
TCP & UDP Header (0) | 2021.10.24 |
struct net_device example (0) | 2021.10.24 |