えいあーるれいの技術日記

ROS2やM5 Stack、Ubuntuについて書いています

IPv4アドレスプログラムの移植(Raspberry Piなど)

Qiitaから引っ越ししました。(せっかくコメントを残してくださったのに申し訳ない…)

リモート接続をするコンピュータ上でプログラムを動かすとき、何かしら固有のIDを自動的に振りたいときがあります。

それはPythonで使いたいときがあれば、C++で使いたいと思うときがあったり、Bashで使いたいと思うときもあったり…

しかし、自分でいちいち探してカスタマイズするのも面倒に感じ、ほとんど同じ挙動をするIPv4アドレス取得プログラムを作ろうと思いました。

この記事では、Raspberry Piでよく使われる言語であるC・C++PythonBash・Rustの5種類においてIPv4アドレスを取得する関数を作成しました。プログラム作成や移植に役立ててください。

動作環境

  • OS : Ubuntu20
  • Python : Python3.8
  • C++/C : cmake version 3.16.3
  • Rust : cargo 1.54.0

共通の挙動

プログラムは全て以下のリポジトリにUPしています。

https://github.com/Ar-Ray-code/ip4_detector

実際のGitHubソースコードと異なって省略している部分があります。

(例:引数チェックの省略)

get_ip4_address(string)

get_ip4_address_all()

lo以外のIPv4アドレスの文字列が格納されたリストを作成します。ネットワークアダプタ名とのリンクは行っていません。(プログラムを改造したらできると思います。)

C言語は実装していません。

  • 引数:なし
  • 返り値:lo以外のIPv4アドレスのリスト(C++Vector(std::string)型で返されます)

C/C++

CとC++IPv4アドレスを取得する部分については同じプログラムで動きます。

参考サイト

get_ip4_address(string)

// get_ip.h
#ifndef _GET_IP_H_
#define _GET_IP_H_

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>

#define MAX_IFR 10 //取得するネットワークアダプタの上限値

char* get_ip4_address(const char* ifname) {
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    struct ifreq ifr;

    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1);
    ioctl(fd, SIOCGIFADDR, &ifr);
    close(fd);

    return inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
}
#endif
// show_ip4.cpp show_ip4.c 共通
#include "get_ip.h"

int main(int argc, char *argv[]) {
    printf("%s\n",get_ip4_address(argv[1]));
}

get_ip4_address_all()

C++は可変のリストをVectorで代用します。

// show_ip4_all.cpp

#include "get_ip.h"
#include <iostream>
#include <vector>
#include <sstream>

std::vector<std::string> get_ip4_address_all() {
    struct ifreq ifr[MAX_IFR];
    struct ifconf ifc;
    int fd;
    int nifs, i;

    char* name[MAX_IFR] = {};
    auto ip = new std::vector<std::string>(); //初期は空っぽ

    fd = socket(AF_INET, SOCK_DGRAM, 0);

    ifc.ifc_len = sizeof(ifr);
    ifc.ifc_ifcu.ifcu_buf = (caddr_t) ifr;

    ioctl(fd, SIOCGIFCONF, &ifc);

    nifs = ifc.ifc_len / sizeof(struct ifreq);

    int target_ips_count = 0;
    for (i=0; i<nifs; i++) {
        // if not "lo"
        if (strncmp(ifr[i].ifr_name, "lo", 2) != 0) {
            name[target_ips_count] = ifr[i].ifr_name;
            target_ips_count++;
        }
    }
    // 1度ネットワークアダプタを閉じる
    close(fd);

    // get_ip4_address()を実行して返り値を追加していく
    for (i=0; i<target_ips_count; i++) {
        ip->push_back(std::string(get_ip4_address(name[i])));
    }
    return *ip;
}

int main(int argc, char *argv[])
{
    // IPアドレスのリストを取得
    auto ip_all = get_ip4_address_all();
    for (int i=0; i<ip_all.size(); i++) {
        std::cout << ip_all[i] << std::endl;
    }
}

実行

cd c++_unix/
make
./show_ip4_all_exe

Python

Pythonは、psutilとnetifacesモジュールを使います。

get_ip4_address(string)

#!/bin/python3
import netifaces as ni
import psutil
import sys

def ip_get(interface:str)->str:
    i = 0
    result = []
    address_list = psutil.net_if_addrs()

    for nic in address_list.keys():
        if (interface in ni.interfaces()[i]):
            ip = ni.ifaddresses(ni.interfaces()[i])[ni.AF_INET][0]['addr']
            return ip
        i += 1
    return "0.0.0.0"

if __name__=='__main__':
    argv = sys.argv[1]
    print(ip_get(argv))

get_ip4_address_all()

#!/bin/python3
import netifaces as ni
import psutil

def ip_get():
    i = 0
    result = []
    address_list = psutil.net_if_addrs()
    
    for nic in address_list.keys():
        if ("lo" not in ni.interfaces()[i]):
            ip = ni.ifaddresses(ni.interfaces()[i])[ni.AF_INET][0]['addr']
            result.append(ip)
        i = i + 1
    return result

if __name__=='__main__':
    print(ip_get())

Rust

Rustはpnetというモジュールを使ってIPアドレスを取得できます。Cargo使ってください。

# Cargo.toml
[package]
name = "show_ip4"
version = "0.1.0"
edition = "2018"

[dependencies]
pnet = "0.28.0"

get_ip4_address(&str)

extern crate pnet;

use std::env;
use pnet::datalink;

fn get_ip4_address(interface_name: &str) -> String{
    for iface in datalink::interfaces() {
        let ips = iface.ips;
        let mut i = 0;
        if !(iface.name == "lo") {
            for ip in ips { 
                if i == 0 {
                    let addr = ip.to_string();
                    if iface.name == interface_name {
                        let addr_fixed: Vec<&str> = addr.split("/").collect();
                        return addr_fixed[0].to_string();
                    }
                }
                i += 1;
            }
        }
    }
    return "".to_string();
}

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{}", get_ip4_address(&args[1]));
}

get_ip4_address_all()

extern crate pnet;

use pnet::datalink;

fn get_ip4_address_all() -> Vec<String> {
    let mut vec_ip4 = Vec::new();

    for iface in datalink::interfaces() {
        let ips = iface.ips;
        let mut i = 0;
        if !(iface.name == "lo") {
            for ip in ips { 
                if i == 0 {
                    let addr = ip.to_string();
                    let addr_fixed: Vec<&str> = addr.split("/").collect();
                    vec_ip4.push(addr_fixed[0].to_string());
                }
                i += 1;
            }
        }
    }
    return vec_ip4;
}

fn main() {
    println!("{:?}", get_ip4_address_all());
}

Bash

Bashは自分自身でネットワーク接続確認とリセットが可能なので、BashでのIPアドレス取得を推奨します。コマンドを工夫することで1行で取得可能です。

また、ネットワークアダプタ名の一覧を取得することが容易なので、他のプログラムにおけるshow_ip4_addressの引数にしてもいいと思います。

変数に代入する場合は、Bashファイル上で次のように記載してください。

A=`ip -4 a | grep -oP '(?<=:\s).+(?=:)'`
echo $A

show_interfaces

echo `ip -4 a | grep -oP '(?<=:\s).+(?=:)'`
# 実行結果例:lo wlan0 docker0

show_ip4

egrep -v '^127'は、lo除外のために使用しています。$1wlan0などにして実行してみてください。

echo `ip -4 a | grep $1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | egrep -v '^127'`
# 実行結果例:172.17.0.1(引数$1がdocker0だった場合)

show_ip4_all

echo `ip -4 a | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127`
# 実行結果例:10.2.82.26 172.17.0.1

リンク

https://github.com/Ar-Ray-code/ip4_detector

https://www.geekpage.jp/programming/linux-network/get-ipaddr.php

https://www.geekpage.jp/programming/linux-network/get-iflist.php

https://docs.rs/pnet/0.28.0/pnet/

https://pypi.org/project/netifaces/