背景
我正在编写一个进行TCP / IP连接的守护进程。它将在具有多个(非环回)IP地址的计算机上运行。我希望用户能够在守护程序的配置文件中指定用于传出连接的IP地址,或*
使用所有IP地址。
地址将用于轮换,每个连接从最近最少使用的IP地址发出。这种行为很重要,因为*
是“all”的替代,因此在多台机器上运行的守护进程可以指向文件共享上的相同配置文件,并且每个都使用自己的IP地址集。
问题:
如何获取机器可以传出(即连接到任何其他计算机)连接的所有IP地址列表?给定所有IP地址的列表,我将如何过滤掉环回地址?
我在C中,如果可能的话我只想使用POSIX,但守护进程可能只能在Linux机器上运行,所以我接受以Linux为中心的答案。
每个IP地址都可以在一个(可能是虚拟的)网络设备上使用,反之亦然,因此枚举网络设备和获取相关IP地址的方法也足够了,尽管我对此并不满意。 (附带问题:甚至可以将多个IP地址与单个设备关联起来吗?多个设备下的相同IP怎么样?不重要。)
解决方案不足:
gethostname()
/ gethostbyname()
(作为this question)。使用该方法,我只返回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's answer给予他信任,指出我需要如何做到这一点。我已经发布了我的own answer列出了如何完全执行此操作的源代码,以防其他人需要它。
答案 0 :(得分:7)
以下是我使用caskey's accepted answer的概念验证代码,为了后人的目的:
#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;
}
像魅力一样!
答案 1 :(得分:6)
这只能以依赖于操作系统的方式完成。您可以尝试解析'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;
};
如您所见,它包含您想要的地址信息。
答案 2 :(得分:1)
对问题的一些答案:
可以使用别名向设备添加多个IP。当你这样做时,Linux会创建名为eth0:0的设备。
ifconfig eth0:0 10.0.0.1
通过频道绑定/链接聚合,可以在多个设备下拥有相同的IP。
答案 3 :(得分:1)
您可以通过几种方式获取所需的接口信息,包括使用SIOCGIFCONF选项调用ioctl()并循环返回的结构以获取接口地址信息。
如果列出了所有IP地址,我将如何过滤掉环回地址?
在caskey的答案中查看ifreq结构。您可以使用以下命令确定环回(正确):
if (ifru_flags & IFF_LOOPBACK)
常量在if.h
中答案 4 :(得分:0)
您确定正确使用gethostname()/ gethostbyname()吗?看看here,我看到的唯一问题就是域名可能有多个映射到它的IP地址。如果是这种情况,则无法知道属于本地计算机的IP地址是什么
答案 5 :(得分:0)
如何获取机器可以传出(即连接到任何其他计算机)连接的所有IP地址列表?给定所有IP地址的列表,我将如何过滤掉环回地址?
查看 lsof 和 netstat 的源代码。您将看到它涉及遍历内核内存结构,而不仅仅是进行系统调用。