当我有多个网络接口时,如何确定所有IP地址?

46
我在计算机上有多个网络接口卡,每个接口卡都有自己的IP地址。
当我使用Python内置的socket模块中的gethostbyname(gethostname())函数时,它只会返回其中一个。如何获取其他的IP地址?

1
你能否将标题中的“address”改为“addresses”,因为这更好地反映了问题(第一眼看到,多个NICs->多个IP地址)。 - Rob
3
我本来只想写“完成”这个评论,但它太短了,需要加到10个字符。 - Harley Holcombe
依赖主机名解析是错误的。你不需要主机名来列出IP地址,这些概念只有远程关联。 - Mikhail T.
15个回答

65

使用netifaces模块。由于网络是复杂的,使用netifaces可能有些棘手,但这里是如何实现你想要的:

>>> import netifaces
>>> netifaces.interfaces()
['lo', 'eth0']
>>> netifaces.ifaddresses('eth0')
{17: [{'broadcast': 'ff:ff:ff:ff:ff:ff', 'addr': '00:11:2f:32:63:45'}], 2: [{'broadcast': '10.0.0.255', 'netmask': '255.255.255.0', 'addr': '10.0.0.2'}], 10: [{'netmask': 'ffff:ffff:ffff:ffff::', 'addr': 'fe80::211:2fff:fe32:6345%eth0'}]}
>>> for interface in netifaces.interfaces():
...   print netifaces.ifaddresses(interface)[netifaces.AF_INET]
...
[{'peer': '127.0.0.1', 'netmask': '255.0.0.0', 'addr': '127.0.0.1'}]
[{'broadcast': '10.0.0.255', 'netmask': '255.255.255.0', 'addr': '10.0.0.2'}]
>>> for interface in netifaces.interfaces():
...   for link in netifaces.ifaddresses(interface)[netifaces.AF_INET]:
...     print link['addr']
...
127.0.0.1
10.0.0.2

可以这样稍微更易读一些:

from netifaces import interfaces, ifaddresses, AF_INET

def ip4_addresses():
    ip_list = []
    for interface in interfaces():
        for link in ifaddresses(interface)[AF_INET]:
            ip_list.append(link['addr'])
    return ip_list

如果您想要IPv6地址,请使用AF_INET6而不是AF_INET。如果您想知道为什么netifaces到处都在使用列表和字典,那是因为单台计算机可以拥有多个网卡,每个网卡可以有多个地址,并且每个地址都有自己的选项集。


PS:这个答案已完全测试过,与我之前那个高赞的(但非常错误)答案不同,被删除了。 - Harley Holcombe
1
只是一个简短的注释:在我的本地机器上,netifaces.ifaddresses 在某些情况下可能会返回一个空字典,因此当尝试访问 AF_INET 键时,代码将在这种情况下失败。这是一个小修复,但除此之外是一个很好的答案。 - zvikico
3
我已将其修改为 for link in ifaddresses(interface).get(AF_INET, ())。我还忽略了一些名为 'lo0' 的接口。 - Julio Batista Silva
3
如果一个NIC没有IPv4地址,ip4_addresses()函数会抛出KeyError,并且应该对AF_INET和'addr'都使用dict.get(key)形式。我提交了一个带有修复的答案编辑,但我的编辑被拒绝了(???)。 - Vijay Varadan

23
import socket
[i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None)]

无需额外模块;适用于 Python 2.7、Ubuntu Linux。 - pnovotnak
7
这只列出连接到外部网络的接口地址,而不是所有接口。 - Paul Hoffman
1
这里使用了名称解析 -- 期望它与网络接口匹配。这并不是一个不合理的期望,但可能会失败。即使提问者自己没有意识到,他想要的是类似于 ifconfig 命令输出的等效信息。 - Mikhail T.
1
使用 {i [4] [0]为 ...,无} } 来获取唯一地址集。如果您需要列表,请将它包装在 list (...) 调用中。 - grandchild
1
我希望我能点赞这个回答,但正如@PaulHoffman指出的那样,它不能返回所有接口的IP地址。我需要一个标准库方法来做到这一点,否则只能解析ip a命令的输出了。 - user7851115
我理解你们,但是在2013年它“对我有效”,而且自那以后我就没有再使用Python了。 - Nakilon

15

仅为完整性,另一个选项是使用psutil

tldr;

import socket
import psutil

def get_ip_addresses(family):
    for interface, snics in psutil.net_if_addrs().items():
        for snic in snics:
            if snic.family == family:
                yield (interface, snic.address)

ipv4s = list(get_ip_addresses(socket.AF_INET))
ipv6s = list(get_ip_addresses(socket.AF_INET6))

解释

您需要使用的函数是net_if_addrs。即:

import psutil
psutil.net_if_addrs()

这将导致类似以下的结果(Python 3):

{'br-ae4880aa80cf': [snic(family=<AddressFamily.AF_INET: 2>, address='172.18.0.1', netmask='255.255.0.0', broadcast='172.18.0.1', ptp=None),
                     snic(family=<AddressFamily.AF_PACKET: 17>, address='02:42:e5:ae:39:94', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'docker0': [snic(family=<AddressFamily.AF_INET: 2>, address='172.17.0.1', netmask='255.255.0.0', broadcast='172.17.0.1', ptp=None),
             snic(family=<AddressFamily.AF_PACKET: 17>, address='02:42:38:d2:4d:77', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'eno1': [snic(family=<AddressFamily.AF_PACKET: 17>, address='54:be:f7:0b:cf:a9', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast=None, ptp=None),
        snic(family=<AddressFamily.AF_PACKET: 17>, address='00:00:00:00:00:00', netmask=None, broadcast=None, ptp=None)],
 'wlp2s0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.4', netmask='255.255.255.0', broadcast='192.168.1.255', ptp=None),
            snic(family=<AddressFamily.AF_PACKET: 17>, address='00:21:27:ee:d6:03', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}

(Python 2):

{'br-ae4880aa80cf': [snic(family=2, address='172.18.0.1', netmask='255.255.0.0', broadcast='172.18.0.1', ptp=None),
                     snic(family=17, address='02:42:e5:ae:39:94', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'docker0': [snic(family=2, address='172.17.0.1', netmask='255.255.0.0', broadcast='172.17.0.1', ptp=None),
             snic(family=17, address='02:42:38:d2:4d:77', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'eno1': [snic(family=17, address='54:be:f7:0b:cf:a9', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)],
 'lo': [snic(family=2, address='127.0.0.1', netmask='255.0.0.0', broadcast=None, ptp=None),
        snic(family=17, address='00:00:00:00:00:00', netmask=None, broadcast=None, ptp=None)],
 'wlp2s0': [snic(family=2, address='192.168.1.4', netmask='255.255.255.0', broadcast='192.168.1.255', ptp=None),
            snic(family=17, address='00:21:27:ee:d6:03', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}

注意:由于每个接口可以有多个属于同一家族的地址,因此字典值是列表。

每个snic都是一个namedtuple,包括5个字段:

  • family:地址族,可以是AF_INETAF_INET6psutil.AF_LINK,代表MAC地址。
  • address:主要NIC地址(始终设置)。
  • netmask:子网掩码地址(可以为None)。
  • broadcast:广播地址(可以为None)。
  • ptp:指“点对点”;它是点对点接口上的目标地址(通常是VPN)。广播和ptp是互斥的(可以为None)。

1
由于树莓派上的conda发行版中没有netifaces,我非常喜欢这种方法。跨平台(在Windows和树莓派raspbian 8上进行了测试)。 - NoOneKnowsImaDog

7

通过使用 netifaces 模块,将所有地址放在一行上:

[netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr'] for iface in netifaces.interfaces() if netifaces.AF_INET in netifaces.ifaddresses(iface)]

谢谢@Elemag。这在Python解释器上可以运行,但当我保存为.py文件时就无法运行了。https://stackoverflow.com/questions/49195864/how-to-get-all-ip-addresses-using-python-windows - user9013730
1
@Sabrina 你必须对结果进行一些操作,比如打印出来。代码已经执行了,但是没有以任何方式可视化。 - Elemag

6

https://docs.python.org/3.4/library/socket.html#socket.if_nameindex

socket.if_nameindex()

返回一个包含网络接口信息(索引int,名称string)元组的列表。如果系统调用失败则返回OSError。

可用性:Unix。

自版本3.3起新增。


以下是可以在Python 3.4、UNIX/Linux上运行的代码示例:

#!/env/python3.4
import socket
import fcntl
import struct

def active_nic_addresses():
    """
    Return a list of IPv4 addresses that are active on the computer.
    """

    addresses = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1]

    return addresses

def get_ip_address( NICname ):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', NICname[:15].encode("UTF-8"))
    )[20:24])


def nic_info():
    """
    Return a list with tuples containing NIC and IPv4
    """
    nic = []

    for ix in socket.if_nameindex():
        name = ix[1]
        ip = get_ip_address( name )

        nic.append( (name, ip) )

    return nic

if __name__ == "__main__":

    print( active_nic_addresses() )
    print( nic_info() )

会打印出类似以下的内容:
['192.168.0.2']
[('lo', '127.0.0.1'), ('enp3s0', '192.168.0.2')]

2

正如这个帖子所表明的那样,有很多方法可以实现相同的结果,我建议的方法是利用getaddrinfo()中内置的家庭过滤器,并解析标准化的元组,如下所示:

最初的回答:

from socket import getaddrinfo, AF_INET, gethostname

for ip in getaddrinfo(host=gethostname(), port=None, family=AF_INET):   
    print(ip[4][0])

最初的回答

示例输出:

192.168.55.1
192.168.170.234

3
不好,返回给我 127.0.0.1 - Ciasto piekarz
是的,大部分这些在Windows上都不起作用。 - Addison
刚刚运行了代码,现在已经过去了4年,在我的Windows 11上运行得很好。它获取的是本地接口的IP地址,而不是公共IP地址(除非你将调制解调器直接连接到接口上而没有路由器)。 - chjortlund

1
此代码片段将提供系统中所有可用的IPV4地址列表。
import itertools
from netifaces import interfaces, ifaddresses, AF_INET

links = filter(None, (ifaddresses(x).get(AF_INET) for x in interfaces()))
links = itertools.chain(*links)
ip_addresses = [x['addr'] for x in links]

1

这只适用于Linux,但是这里有一个非常简单的方法 http://code.activestate.com/recipes/439094/

它可能使用了与另一个答案中提到的netifaces包类似的代码(但此处链接的是当前版本)

socket.getaddrinfo()实际上并不返回设备绑定的IP地址。如果您的主机文件包含一行“127.0.1.1 yourhost.example.com yourhost”,这是一种常见的配置,getaddrinfo只会返回127.0.1.1。


0

你可以像这样轻松地完成它:

import netifaces

for interface in netifaces.interfaces():
    print netifaces.ifaddresses(interface)

更多信息请查看netifaces文档


0

这里有一个用于查找所有IPv4和IPv6接口的例程。正如之前的帖子所指出的那样,socket.gethostbyname_ex() 对于IPv6不起作用,Python文档建议使用socket.getaddressinfo()

此例程添加了回调IPv4接口(127.0.0.1),如果有任何IPv6接口,则还会添加回调IPv6接口(::1)。在我的机器上,socket.getaddrinfo()将为我提供其中一个或两个,但仅当我没有其他可用接口时才会提供。

对于我的需求,我想尝试在每个可用接口上的指定端口上打开一个UDP套接字,这就是代码中包含“port”和socket.SOCK_DGRAM的原因。可以安全地更改它们,例如,如果您没有特定的端口。

addrinfo_ipv4 = socket.getaddrinfo(hostname,port,socket.AF_INET,socket.SOCK_DGRAM)
addrinfo_ipv6 = []
try:
    addrinfo_ipv6 = socket.getaddrinfo(hostname,port,socket.AF_INET6,socket.SOCK_DGRAM)
except socket.gaierror:
    pass
addrinfo = [(f,t,a) for f,t,p,cn,a in addrinfo_ipv4+addrinfo_ipv6]
addrinfo_local = [(socket.AF_INET,socket.SOCK_DGRAM,('127.0.0.1',port))]
if addrinfo_ipv6: 
    addrinfo_local.append( (socket.AF_INET6,socket.SOCK_DGRAM,('::1',port)) )
[addrinfo.append(ai) for ai in addrinfo_local if ai not in addrinfo]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接