[英]How to enumerate all IP addresses attached to a machine, in POSIX C?
背景:
我正在編寫一個進行TCP / IP連接的守護進程。 它將在具有多個(非環回)IP地址的計算機上運行。 我希望用戶能夠在守護進程的配置文件中指定用於傳出連接的IP地址,或者*
使用all。
地址將用於輪換,每個連接從最近最少使用的IP地址發出。 這種行為是很重要的,因為是*
是對“所有”等多台機器上運行的守護進程可以指向一個文件共享相同的配置文件的替代者,並讓每個使用它自己的一套IP地址。
問題:
如何獲取機器可以傳出(即連接到任何其他計算機)連接的所有IP地址列表? 給定所有IP地址的列表,我將如何過濾掉環回地址?
我在C中,如果可能的話我只想使用POSIX,但守護進程可能只能在Linux機器上運行,所以我接受一個以Linux為中心的答案。
每個IP地址都可以在一個(可能是虛擬的)網絡設備上使用,反之亦然,因此枚舉網絡設備和獲取相關IP地址的方法也足夠了,盡管我對此並不滿意。 (附帶問題:甚至可以將多個IP地址與單個設備相關聯嗎?多個設備下的相同IP如何?不重要。)
解決方案不足:
gethostname()
/ gethostbyname()
(作為這個問題 )。 使用該方法,我只返回127.0.0.1(或Debian中的.1.1)。 我懷疑這是因為機器的主機名在hosts
文件中,這就是gethostbyname()
檢查。 (我相信這就是為什么在Debian中我總是得到127.0.1.1:Debian默認將localhost添加為127.0.0.1,機器的主機名為127.0.1.1添加到hosts
文件,對吧?)我想要一個忽略hosts
的解決方案並給出我真的在那里。 getaddrinfo()
比gethostname()
/ gethostbyname()
它似乎受到同樣問題的約束。 我測試了這個將機器的主機名和一個NULL
服務(端口)傳遞給它; 文檔說傳遞NULL
主機名和NULL
服務是非法的,這是通過測試備份的。 不知道怎么回答機器上的所有問題,但是我願意接受這方面的建議。 最后的編輯:我已經接受了卡斯基的答案給了他一個功勞,因為我指的是如何做到這一點的方向。 我已經發布了自己的答案,列出了如果有其他人需要它的確切方法的源代碼。
這是我使用caskey接受的答案的概念代碼證明,為了后人的緣故:
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static const char * flags(int sd, const char * name)
{
static char buf[1024];
static struct ifreq ifreq;
strcpy(ifreq.ifr_name, name);
int r = ioctl(sd, SIOCGIFFLAGS, (char *)&ifreq);
assert(r == 0);
int l = 0;
#define FLAG(b) if(ifreq.ifr_flags & b) l += snprintf(buf + l, sizeof(buf) - l, #b " ")
FLAG(IFF_UP);
FLAG(IFF_BROADCAST);
FLAG(IFF_DEBUG);
FLAG(IFF_LOOPBACK);
FLAG(IFF_POINTOPOINT);
FLAG(IFF_RUNNING);
FLAG(IFF_NOARP);
FLAG(IFF_PROMISC);
FLAG(IFF_NOTRAILERS);
FLAG(IFF_ALLMULTI);
FLAG(IFF_MASTER);
FLAG(IFF_SLAVE);
FLAG(IFF_MULTICAST);
FLAG(IFF_PORTSEL);
FLAG(IFF_AUTOMEDIA);
FLAG(IFF_DYNAMIC);
#undef FLAG
return buf;
}
int main(void)
{
static struct ifreq ifreqs[32];
struct ifconf ifconf;
memset(&ifconf, 0, sizeof(ifconf));
ifconf.ifc_req = ifreqs;
ifconf.ifc_len = sizeof(ifreqs);
int sd = socket(PF_INET, SOCK_STREAM, 0);
assert(sd >= 0);
int r = ioctl(sd, SIOCGIFCONF, (char *)&ifconf);
assert(r == 0);
for(int i = 0; i < ifconf.ifc_len/sizeof(struct ifreq); ++i)
{
printf("%s: %s\n", ifreqs[i].ifr_name, inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr));
printf(" flags: %s\n", flags(sd, ifreqs[i].ifr_name));
}
close(sd);
return 0;
}
奇跡般有效!
這只能以依賴於操作系統的方式完成。 您可以嘗試解析'iptables'的輸出,但linux的正確答案是使用ioctl。
SIOCGIFCONF takes a struct ifconf *. The ifc_buf field points to a buffer of length ifc_len bytes, into which the kernel writes a list of type struct ifreq [].
struct ifreq在linux / if.h中有記錄:
struct ifreq
{
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
如您所見,它包含您想要的地址信息。
對問題的一些答案:
可以使用別名將多個IP添加到設備。 執行此操作時,Linux會創建名為eth0:0的設備。
ifconfig eth0:0 10.0.0.1
在多個設備下具有相同的IP可以通過信道綁定/鏈路聚合來完成。
您可以通過幾種方式獲取所需的接口信息,包括使用SIOCGIFCONF選項調用ioctl()並循環返回的結構以獲取接口地址信息。
給定所有IP地址的列表,我將如何過濾掉環回地址?
請參閱caskey的答案中的ifreq結構。 您可以使用以下命令確定環回(正確):
if (ifru_flags & IFF_LOOPBACK)
常量在if.h中
你確定你正確使用gethostname()/ gethostbyname()嗎? 看看這里 ,我看到的唯一問題是,域名可能有多個映射到它的IP地址。 如果是這種情況,則無法知道屬於本地計算機的IP地址是什么
如何獲取機器可以傳出(即連接到任何其他計算機)連接的所有IP地址列表? 給定所有IP地址的列表,我將如何過濾掉環回地址?
查看lsof和netstat的源代碼。 您將看到它涉及遍歷內核內存結構,而不僅僅是進行系統調用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.