[Linux] 嵌入式系統不可或缺的工具 – busybox 分析 ifconfig command

Busybox

玩過嵌入式系統的使用者,一定都會知道 Busybox,它提供一些小型 Linux command,方便在 console 端使用,以及一些 C 語言或者是 shell script 裡面,大家都知道 ifconfig 這指令,為了從 Kernel 2.6.15 轉換到 2.6.34.7 版本,原本的 Busybox 版本只有 1.0.1,現在已經到 1.18.1,轉換過程改了 Kernel netfilter 部份,以及 user space 部份 iptables extension。ifconfig 是 Busybox 其中一個指令用來查看目前有多少網路介面(network interface),來看看他是如何得到這些 interface 資訊,包含介面名稱、type、IP Adress、IP network mask、HW address 等….。

要讀取 interface 相關資訊可以透過兩種方式,一種是讀取 (IPv6 是 /proc/net/if_inet6),另一種透過 Socket 連接SOCK_DGRAM,最後用 iotcl 方式讀取 interface 相關資料,busybox 會先偵測檔案 /proc/net/dev 是否存在,如果 Kernel 有支援,就會讀取此檔案,如果不存在,則利用 socket 讀取資料。

if_readlist_proc 函式裡面:

1
2
3
4
fh = fopen_or_warn(_PATH_PROCNET_DEV, "r");
if (!fh) {
    return if_readconf();
}

看一下 /proc/net/dev 內容

1
2
3
4
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo:     104       1    0    0    0     0          0         0      104       1    0    0    0     0       0          0
  eth0:21798505   51360    0    0    0     0          0         0  7693686   46844    0    0    0     0       0          0

首先濾掉前兩行不需要的資料,在利用 get_name 函式抓取 lo 跟 eth0 interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fgets(buf, sizeof buf, fh); /* eat line */
fgets(buf, sizeof buf, fh);

procnetdev_vsn = procnetdev_version(buf);

err = 0;
while (fgets(buf, sizeof buf, fh)) {
    char *s, name[128];
    s = get_name(name, buf);
    ife = add_interface(name);
    get_dev_fields(s, ife, procnetdev_vsn);
    ife->statistics_valid = 1;
    if (target && !strcmp(target, name))
        break;
}

get_name 直接濾掉每行冒號 : 後面的資料,並且將其加入 interface structure 雙向 link list

 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
static char *get_name(char *name, char *p)
{
    /* Extract <name> from nul-terminated p where p matches
     * <name>: after leading whitespace.
     * If match is not made, set name empty and return unchanged p
     */
    char *nameend;
    char *namestart = skip_whitespace(p);

    nameend = namestart;
    while (*nameend && *nameend != ':' && !isspace(*nameend))
        nameend++;
    if (*nameend == ':') {
        if ((nameend - namestart) < IFNAMSIZ) {
            memcpy(name, namestart, nameend - namestart);
            name[nameend - namestart] = '\0';
            p = nameend;
        } else {
            /* Interface name too large */
            name[0] = '\0';
        }
    } else {
        /* trailing ':' not found - return empty */
        name[0] = '\0';
    }
    return p + 1;
}

add_interface 將 network interface 加入 link list

 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
static struct interface *add_interface(char *name)
{
    struct interface *ife, **nextp, *new;

    for (ife = int_last; ife; ife = ife->prev) {
        int n = /*n*/strcmp(ife->name, name);

        if (n == 0)
            return ife;
        if (n < 0)
            break;
    }

    new = xzalloc(sizeof(*new));
    strncpy_IFNAMSIZ(new->name, name);
    nextp = ife ? &ife->next : &int_list;
    new->prev = ife;
    new->next = *nextp;
    if (new->next)
        new->next->prev = new;
    else
        int_last = new;
    *nextp = new;
    return new;
}

最後將 /proc/net/dev 剩餘資訊利用 get_dev_fields 讀取 sscanf

 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
static void get_dev_fields(char *bp, struct interface *ife, int procnetdev_vsn)
{
    memset(&ife->stats, 0, sizeof(struct user_net_device_stats));

    sscanf(bp, ss_fmt[procnetdev_vsn],
           &ife->stats.rx_bytes, /* missing for 0 */
           &ife->stats.rx_packets,
           &ife->stats.rx_errors,
           &ife->stats.rx_dropped,
           &ife->stats.rx_fifo_errors,
           &ife->stats.rx_frame_errors,
           &ife->stats.rx_compressed, /* missing for <= 1 */
           &ife->stats.rx_multicast, /* missing for <= 1 */
           &ife->stats.tx_bytes, /* missing for 0 */
           &ife->stats.tx_packets,
           &ife->stats.tx_errors,
           &ife->stats.tx_dropped,
           &ife->stats.tx_fifo_errors,
           &ife->stats.collisions,
           &ife->stats.tx_carrier_errors,
           &ife->stats.tx_compressed /* missing for <= 1 */
           );

    if (procnetdev_vsn <= 1) {
        if (procnetdev_vsn == 0) {
            ife->stats.rx_bytes = 0;
            ife->stats.tx_bytes = 0;
        }
        ife->stats.rx_multicast = 0;
        ife->stats.rx_compressed = 0;
        ife->stats.tx_compressed = 0;
    }
}

另外要得到 IP Address 資訊,就必須用 if_fetch 函式,透過 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* Fetch the interface configuration from the kernel. */
static int if_fetch(struct interface *ife)
{
    struct ifreq ifr;
    char *ifname = ife->name;
    int skfd;

    skfd = xsocket(AF_INET, SOCK_DGRAM, 0);

    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
        close(skfd);
        return -1;
    }
    ife->flags = ifr.ifr_flags;

    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    memset(ife->hwaddr, 0, 32);
    if (ioctl(skfd, SIOCGIFHWADDR, &ifr) >= 0)
        memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8);

    ife->type = ifr.ifr_hwaddr.sa_family;

    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    ife->metric = 0;
    if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0)
        ife->metric = ifr.ifr_metric;

    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    ife->mtu = 0;
    if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0)
        ife->mtu = ifr.ifr_mtu;

    memset(&ife->map, 0, sizeof(struct ifmap));
#ifdef SIOCGIFMAP
    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0)
        ife->map = ifr.ifr_map;
#endif

#ifdef HAVE_TXQUEUELEN
    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    ife->tx_queue_len = -1;  /* unknown value */
    if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) >= 0)
        ife->tx_queue_len = ifr.ifr_qlen;
#else
    ife->tx_queue_len = -1;  /* unknown value */
#endif

    strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
    ifr.ifr_addr.sa_family = AF_INET;
    memset(&ife->addr, 0, sizeof(struct sockaddr));
    if (ioctl(skfd, SIOCGIFADDR, &ifr) == 0) {
        ife->has_ip = 1;
        ife->addr = ifr.ifr_addr;
        strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
        memset(&ife->dstaddr, 0, sizeof(struct sockaddr));
        if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0)
            ife->dstaddr = ifr.ifr_dstaddr;

        strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
        memset(&ife->broadaddr, 0, sizeof(struct sockaddr));
        if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
            ife->broadaddr = ifr.ifr_broadaddr;

        strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
        memset(&ife->netmask, 0, sizeof(struct sockaddr));
        if (ioctl(skfd, SIOCGIFNETMASK, &ifr) >= 0)
            ife->netmask = ifr.ifr_netmask;
    }

    close(skfd);
    return 0;
}

以上就是執行 ifconfig command 的流程,如果有任何問題可以直接留言 ^^


See also