[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 函式裡面:
fh = fopen_or_warn(_PATH_PROCNET_DEV, "r");
if (!fh) {
    return if_readconf();
}
看一下 /proc/net/dev 內容
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
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
static char *get_name(char *name, char *p)
{
	/* Extract  from nul-terminated p where p matches
	 * : 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;
}[/code]

add_interface 將 network interface 加入 link list
[code lang="C"]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
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 進行讀取
/* 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 的流程,如果有任何問題可以直接留言 ^^