Aug 27, 2026

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;
        

Edit this entry...

wikieditish message: Ready to edit this entry.
















A quick preview will be rendered here when you click "Preview" button.