2026/08/27
Linux ネットワークプログラミング
Linux 上で動作するネットワーク関係の C プログラムのスニペットです。
一部のコードは Linux 固有の機能を使っていますので残念ながら Linux 上でしか動作しません。
一応の動作確認は実施していますが、処理の概要を示しただけなのでエラー処理等は省いています。
実際に製品に組み込む場合はエラーチェック処理は必須です。
指定されたインタフェイスの情報を取得する
オープンされた L3 層の UDP ソケット (ネットワーク層) に対して ioctl(2) を発行する事で各種情報をカーネルから取得する事ができます。 ここでは例として以下の情報を取得して表示するコードを示します。
- アドレス
- ネットマスク
- MAC アドレス
1/* ioctl(2) 用に socket を作成 */ 2if((sock = socket(AF_INET, SOCK_DGRAM, 0)) >= 0){ 3 const char *ifname = "eth0"; 4 struct ifreq ifr; 5 6 /* ioctl(2) 用にインタフェイス名をセット */ 7 memset(&ifr, 0, sizeof(ifr)); 8 strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); 9 10 /* ioctl(2) を発行して IP アドレスを取得 */ 11 if(ioctl(sock, SIOCGIFADDR, &ifr) >= 0){ 12 struct sockaddr_in *src_ip_addr = (struct sockaddr_in *)&ifr.ifr_addr; 13 printf("IP Address: %s\n", inet_ntoa(src_ip_addr->sin_addr)); 14 } 15 16 /* ioctl(2) を発行してネットマスクを取得 */ 17 if(ioctl(sock, SIOCGIFNETMASK, &ifr) >= 0){ 18 struct sockaddr_in *netmask_addr = (struct sockaddr_in *)&ifr.ifr_netmask; 19 printf("Netmask: %s\n", inet_ntoa(netmask_addr->sin_addr)); 20 } 21 /* ioctl(2) を発行して MAC アドレスを取得 */ 22 if(ioctl(sock, SIOCGIFHWADDR, &ifr) >= 0){ 23 unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data; 24 printf("MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", 25 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 26 } 27}
指定されたインタフェイスの対向アドレスを取得する
指定されたインタフェイスが Peer to Peer の場合はローカルと対向の IPv4 アドレスを取得します。 指定されたインタフェイスが Peer to Peer 以外の場合はどちらも同じ値となります。 L3 層の DGRAM ソケット (UDP ソケット) を使用しているので通常のユーザにも実行可能です。
1if((sock = socket(AF_INET, SOCK_DGRAM, 0)) >= 0){ 2 const char *ifname = "eth0"; 3 struct ifreq ifr; 4 5 /* ioctl(2) 用にインタフェイス名をセット */ 6 memset(&ifr, 0, sizeof(ifr)); 7 strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); 8 9 /* ioctl(2) を発行して自分自身のアドレスを取得 */ 10 if(ioctl(sock, SIOCGIFADDR, &ifr) >= 0){ 11 struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_dstaddr; 12 } 13 14 /* ioctl(2) を発行して対向のアドレスを取得 */ 15 if(ioctl(sock, SIOCGIFDSTADDR, &ifr) >= 0){ 16 struct sockaddr_in *sin2 = (struct sockaddr_in *)&ifr.ifr_dstaddr; 17 } 18}
指定されたインタフェイスと通信可能なアドレスの一覧取得
こちらは Linux 特有の NetLink という仕組みを利用して 指定されたインタフェイスの ARP テーブルを参照して通信可能な IPv4 アドレスを取得します。 サンプルではチェック処理などを省いていますが、 実際には環境によって大量のアドレスが取得できるので受け取り側のオーバーフローには要注意です。
1const char *ifname = "eth0"; 2unsigned int index = if_nametoindex(ifname); 3 4if(index > 0){ 5 /* Netlink ソケットを開く */ 6 if((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) >= 0){ 7 8 /* リクエストメッセージの組み立て (RTM_GETNEIGH = ARPテーブル取得) */ 9 memset(&req, 0, sizeof(req)); 10 req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); 11 req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; 12 req.nlh.nlmsg_type = RTM_GETNEIGH; 13 req.ndm.ndm_family = AF_INET; /* IPv4を指定 */ 14 15 /* カーネルに問い合わせ */ 16 if(send(sock, &req, req.nlh.nlmsg_len, 0) >= 0){ 17 /* カーネルからの応答を受信・パース */ 18 while((status = recv(sock, buff, sizeof(buff), 0)) > 0){ 19 struct nlmsghdr *nlh; 20 21 for(nlh=(struct nlmsghdr *)buff; NLMSG_OK(nlh, status); nlh=NLMSG_NEXT(nlh, status)){ 22 /* メッセージ終了(DONE)ならループを抜ける */ 23 if((nlh->nlmsg_type == NLMSG_DONE) || (nlh->nlmsg_type == NLMSG_ERROR) 24 break; 25 26 struct ndmsg *ndm = (struct ndmsg *)NLMSG_DATA(nlh); 27 /* 対象のインターフェースインデックスと一致しない場合や 28 * 完全に死んでいるもの (NUD_FAILED や NUD_INCOMPLETE) は除外する 29 */ 30 if((ndm->ndm_ifindex != index) || (ndm->ndm_state & (NUD_INCOMPLETE | NUD_FAILED))) 31 continue; 32 33 /* 属性(IPアドレスやMACアドレス)をパース */ 34 struct rtattr *rta = (struct rtattr *)((char *)ndm + NLMSG_ALIGN(sizeof(struct ndmsg))); 35 int rta_len; 36 37 for(rta_len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg)); 38 RTA_OK(rta, rta_len); 39 rta = RTA_NEXT(rta, rta_len)) 40 /* NDA_DST が宛先IPアドレス(ARPテーブルのIP)を示す */ 41 if(rta->rta_type == NDA_DST) 42 ipaddr[i++] = *(unsigned long *)RTA_DATA(rta); 43 } 44 } 45 } 46 close(sock); 47 }
IPアドレスとネットマスクを元にユニキャストアドレスを抽出
IPv4アドレスの一覧からブロードキャストアドレスやネットマスクなどの情報を除外して ユニキャストアドレスだけを抽出する処理です。
1unsigned long target_ip, 2 my_ip, 3 netmask; 4 5/* 255.255.255.255 もしくは 0.0.0.0 は除外 */ 6if(target_ip == 0xffffffff || target_ip == 0) 7 return 0; 8 9/* マルチキャストアドレス (224.0.0.0 - 239.255.255.255) を除外 */ 10/* target_ip の第 1 オクテットをチェック */ 11unsigned char *ip_bytes = (unsigned char *)&target_ip; 12 13if(ip_bytes[0] >= 224 && ip_bytes[0] <= 239) 14 return 0; 15 16unsigned long network_addr = my_ip & netmask, 17 broadcast_addr = my_ip | ~netmask; 18 19/* ネットマスクもしくはブロードキャストアドレスを除外 */ 20if(target_ip == network_addr || target_ip == broadcast_addr) 21 return 0; 22else if(ip_bytes[3] == 255 || ip_bytes[3] == 0) 23 return 0;
IPv4アドレスがネットワークに属しているか確認する
1const char *net, 2 *mask, 3 *addr; 4 5if(inet_pton(AF_INET, network, &net) == 1 && 6 inet_pton(AF_INET, netmask, &mask) == 1 && 7 inet_pton(AF_INET, ipaddr, &addr) == 1){ 8 9 // ネットワークアドレス側はすでにマスク済みの想定であれば、左辺のマスクは省略可能です 10 return(addr.s_addr & mask.s_addr) == net.s_addr; 11}
CIDR 形式にも対応させる
1const char *net, 2 *mask 3 *addr; 4 5if(prefix >= 0 && prefix <= 32){ 6 if(inet_pton(AF_INET, network, &net) == 1 && 7 inet_pton(AF_INET, ipaddr, &addr) == 1){ 8 /* プレフィックス長からホストバイトオーダーでマスクを作成して 9 * ネットワークバイトオーダー(ビッグエンディアン)に変換 10 */ 11 uint32_t mask = htonl((prefix == 0) ? 0 : (~0U << (32 - prefix))); 12 13 return(addr.s_addr & mask) == (net.s_addr & mask); 14 } 15}
複数回実行される場合の高速化
ループ内など複数回実行される場合は文字や変換処理など inet_pton(3) がボトルネックになってしまうので、 あらかじめネットワークアドレスとネットマスクを数値データ (uinit_32) に変換して保持しておいた方が高速化できます。
1/* ネットワーク情報を保持しておく構造体 */ 2typedef struct{ 3 uint32_t net; 4 uint32_t mask; 5} rule; 6 7/* あらかじめ IPv4 アドレスも unit32 に変換する */ 8uinit_32 addr; 9 10/* 判定処理は数値演算のみ */ 11return(addr & rule->mask) == rule->net;

