普通にifconfigで調べられますが、ソケットプログラミングの勉強として、指定したインターフェースのMACアドレスを取得してみました。
インタフェースを操作するには、基本的にはifreq構造体とioctlシステムコールの2つを使います。なお、ioctlシステムコールを使うためには、ソケットを用意する必要があります。
コードはこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/ioctl.h> #include<sys/socket.h> #include<net/if.h> #include<net/ethernet.h> #include<netpacket/packet.h> #include<arpa/inet.h> int main(void) { struct ifreq ifreq; int s=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if(s<0){ perror("socket"); } memset(&ifreq, 0, sizeof(struct ifreq)); strncpy(ifreq.ifr_name, "enp0s3", sizeof(ifreq.ifr_name)-1); if(ioctl(s, SIOCGIFHWADDR, &ifreq)!=0) perror("ioctl"); printf("%02x:%02x:%02x:%02x:%02x:%02x \n",(unsigned char)ifreq.ifr_hwaddr.sa_data[0], (unsigned char)ifreq.ifr_hwaddr.sa_data[1], (unsigned char)ifreq.ifr_hwaddr.sa_data[2], (unsigned char)ifreq.ifr_hwaddr.sa_data[3], (unsigned char)ifreq.ifr_hwaddr.sa_data[4], (unsigned char)ifreq.ifr_hwaddr.sa_data[5]); close(s); return 0; } |
まずはifreq構造体に操作対象とするインターフェース名を入れます。
ifreq構造体は、インタフェース情報を格納しておくためのプログラム上の格納場所です。このとき、存在しないインターフェースを指定すると、エラーになりますので注意です。今回私は"enp0s3"を指定しましたが、指定するときはインタフェース名をifconfigコマンドで確認しましょう。
インターフェース名を入れたら、次はioctlでMACアドレスを取得します。
ioctlはネットワークインタフェースを操作するシステムコールです。
今回は、ioctlの"SIOCGIFHWADDR"というフラグを使って、MACアドレスを取得します。ioctlは取得と同時に、ifreq構造体のifr_hwaddrというメンバに格納してれくれます。なおioctlを使うため、事前にリンクレイヤを扱うrawソケットを生成しておかなければいけません。
参考までにifreq構造体は/usr/include/linux/if.hで定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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 * ifru_data; struct if_settings ifru_settings; } ifr_ifru; }; #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*/ |
ifreq構造体のメンバを見ていくと、ifr_hwaddrがあります。厳密にはifru_hwaddrとして宣言されていますが、#defineのところで「ifr_ifru.ifru_hwaddrをifr_hwaddrとする」と定義されています。なので実際は、ifreq構造体のifr_hwaddrとしてメンバにアクセスできるようになります。
しかし少し複雑なことに、このMACアドレスを格納するifr_hwaddrはsockaddr構造体として宣言されています。構造体の中に、メンバとしてさらに構造体が宣言されています。初見だと分かりにくいですね。
なのでMACアドレスを操る際は、さらにsockaddr構造体のメンバにアクセスしなければいけません。sockaddr構造体は/usr/include/bits/socket.hに定義されています。
1 2 3 4 5 6 |
/* Structure describing a generic socket address. */ struct sockaddr { __SOCKADDR_COMMON (sa_); /* Common data: address family and length. */ char sa_data[14]; /* Address data. */ }; |
sockaddr構造体はアドレス情報をひとまとめに格納した構造体です。char型の配列としてsa_data[14]と宣言されています。これの[0]から[5]のところにMACアドレスが入っています。printfで表示してみると分かりますが、[6]~[13]は0が入っています。
MACアドレスはifr_hwaddrのsa_dataに格納されていることが、分かったので最後にそれを出力するだけです。実行結果が、ifconfigで表示されたMACアドレスと同じか確認してみてください。一致していることが確認できるかと思います!
最後まで読んでいただきありがとうございました。