【问题标题】:How can I get the IP address from a NIC (network interface controller) in Python?如何从 Python 中的 NIC(网络接口控制器)获取 IP 地址?
【发布时间】:2014-08-03 12:26:01
【问题描述】:

当 Unix 上的 Python 脚本发生错误时,会发送一封电子邮件。

如果测试服务器的 IP 地址是 192.168.100.37,我被要求将 {Testing Environment} 添加到电子邮件的主题行。这样我们就可以拥有一个脚本版本和一种方法来判断电子邮件是否来自测试服务器上的混乱数据。

但是,当我用谷歌搜索时,我一直在寻找这段代码:

import socket
socket.gethostbyname(socket.gethostname())

但是,这给了我 127.0.1.1 的 IP 地址。当我使用ifconfig 时,我明白了

eth0      Link encap:Ethernet  HWaddr 00:1c:c4:2c:c8:3e
          inet addr:192.168.100.37  Bcast:192.168.100.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:75760697 errors:0 dropped:411180 overruns:0 frame:0
          TX packets:23166399 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:59525958247 (59.5 GB)  TX bytes:10142130096 (10.1 GB)
          Interrupt:19 Memory:f0500000-f0520000

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:25573544 errors:0 dropped:0 overruns:0 frame:0
          TX packets:25573544 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:44531490070 (44.5 GB)  TX bytes:44531490070 (44.5 GB)

首先,我不知道它从哪里得到 127.0.1.1,但无论如何这都不是我想要的。当我用谷歌搜索时,我不断使用相同的语法,Bash 脚本或 netifaces,并且我正在尝试使用标准库。

那么如何在 Python 中获取 eth0 的 IP 地址呢?

【问题讨论】:

  • “我不知道它从哪里得到 127.0.1.1”来自 /etc/hosts 文件。
  • 你提到了,“127.0.1.1”。您是说“127.0.0.1”吗?因为那是您的本地环回接口,loifconfig 输出中的第二个条目);如果您cat /etc/hosts,则为第一个条目。

标签: python unix networking nic


【解决方案1】:

两种方法:

方法#1(使用外部包)

您需要询问绑定到eth0 接口的IP 地址。这可从netifaces package 获得

import netifaces as ni
ni.ifaddresses('eth0')
ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
print(ip)  # should print "192.168.100.37"

您还可以通过

获取所有可用接口的列表
ni.interfaces()

方法#2(无外部封装)

这是一种不使用python包获取IP地址的方法:

import socket
import fcntl
import struct

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

get_ip_address('eth0')  # '192.168.0.110'

注意:通过检测 IP 地址来确定您使用的环境是非常困难的。几乎所有框架都提供了一种非常简单的方法来设置/修改环境变量以指示当前环境。尝试查看您的文档。应该像做一样简单

if app.config['ENV'] == 'production':
  #send production email
else:
  #send development email

【讨论】:

  • 我知道我迟到了,但有人能解释一下编辑 2 的魔法吗?我是套接字库的新手,从未使用过其他两个。
  • 为什么使用 ifname[:15] 而不是 ifname?
  • 对于 Python 3,ifname[:15] 应该是 bytes(ifname[:15], 'utf-8')
  • 鉴于它是 Python,将 netifaces 导入为 ni 特别合适……至少对于某些骑士而言。 ? ;-)
  • 方法 2 每次调用时都会泄漏一个套接字。您需要在 finally 块中调用 s.close()。
【解决方案2】:

或者,如果您想获取用于连接网络的任何接口的 IP 地址,而无需知道其名称,您可以使用以下方法:

import socket
def get_ip_address():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    return s.getsockname()[0]

我知道这与您的问题有些不同,但其他人可能会来到这里并发现这个问题更有用。您不必拥有到 8.8.8.8 的路由即可使用它。它所做的只是打开一个套接字,但不发送任何数据。

【讨论】:

  • 是的,可能不会去任何地方,但某些 LAN 可能不会暴露在它面前。
  • 这里不需要关闭socket吗?
  • @krizajb 我从来没有打扰过,因为我从不为每个 pid 做一次以上,如果在 gc 期间没有释放单个文件描述符,它将被 pid 清理。如果由于某种原因您需要做很多事情,那么使用上下文管理器将是 pythonic stackoverflow.com/questions/16772465
  • 不明白连接8.8.8.8的目的
  • @k4ppa 连接到 8.8.8.8 会强制系统进行外部连接以解析完整路由。反过来,这会为此系统提供面向物理网络(或面向网络之一)的 IP 号(基于内核为此连接选择的任何适配器)。如果没有连接,您将获得 0.0.0.0 作为套接字名称。 8.8.8.8 没有什么神奇之处,除了它(可能)总是在那里并且肯定能够处理流量。在非 Internet 连接的专用网络上,您将使用该网络上的某个服务器编号(或名称)。
【解决方案3】:

如果你只需要在 Unix 上工作,你可以使用系统调用(参考堆栈溢出问题 Parse ifconfig to get only my IP address using Bash):

import os
f = os.popen('ifconfig eth0 | grep "inet\ addr" | cut -d: -f2 | cut -d" " -f1')
your_ip=f.read()

【讨论】:

  • 这个是特定于发行版的。例如,在 RHEL 上,cut 命令会切断错误的术语。所以这个不能跨 *nix 发行版移植。
  • grepcut 这里真的没必要,直接获取输出解析即可。此外,ip addr 命令可能更受欢迎。
【解决方案4】:

试试下面的代码。它适用于Mac OS X v10.10.2(优胜美地):

import subprocess

if __name__ == "__main__":
    result = subprocess.check_output('ifconfig en0 |grep -w inet', shell=True) # you may need to use eth0 instead of en0 here!!!
    print 'output = %s' % result.strip()
    # result = None
    ip = ''
    if result:
        strs = result.split('\n')
        for line in strs:
            # remove \t, space...
            line = line.strip()
            if line.startswith('inet '):
                a = line.find(' ')
                ipStart = a+1
                ipEnd = line.find(' ', ipStart)
                if a != -1 and ipEnd != -1:
                    ip = line[ipStart:ipEnd]
                    break
    print 'ip = %s' % ip

【讨论】:

    【解决方案5】:

    对我有用

     import subprocess
     my_ip = subprocess.Popen(['ifconfig eth0 | awk "/inet /" | cut -d":" -f 2 | cut -d" " -f1'], stdout=subprocess.PIPE, shell=True)
     (IP,errors) = my_ip.communicate()
     my_ip.stdout.close()
     print IP
    

    【讨论】:

    • 我的猜测是有人对此进行了 -1 处理,因为 Linux 在 ifconfig 输出中没有使用“:”分隔符。此命令仅适用于包含此类内容的某些 Unix。
    【解决方案6】:

    在 ifconfig 中找到第一个正在运行的 eth/wlan 条目的 IP 地址:

    import itertools
    import os
    import re
    
    def get_ip():
        f = os.popen('ifconfig')
        for iface in [' '.join(i) for i in iter(lambda: list(itertools.takewhile(lambda l: not l.isspace(), f)), [])]:
            if re.findall('^(eth|wlan)[0-9]', iface) and re.findall('RUNNING', iface):
                ip = re.findall('(?<=inet\saddr:)[0-9\.]+', iface)
                if ip:
                    return ip[0]
        return False
    

    【讨论】:

      【解决方案7】:

      由于大多数答案使用 ifconfigeth0 接口中提取 IPv4 地址,该接口已被 ip addr 弃用,因此可以使用以下代码:

      import os
      
      ipv4 = os.popen('ip addr show eth0 | grep "\<inet\>" | awk \'{ print $2 }\' | awk -F "/" \'{ print $1 }\'').read().strip()
      ipv6 = os.popen('ip addr show eth0 | grep "\<inet6\>" | awk \'{ print $2 }\' | awk -F "/" \'{ print $1 }\'').read().strip()
      

      或者,您可以使用split() 代替grepAWK 将部分解析任务转移到Python 解释器,如注释中的Sergiy Kolodyazhnyy points out

      import os
      
      ipv4 = os.popen('ip addr show eth0').read().split("inet ")[1].split("/")[0]
      ipv6 = os.popen('ip addr show eth0').read().split("inet6 ")[1].split("/")[0]
      

      但在这种情况下,您必须检查每个 split() 调用返回的数组的边界。


      使用正则表达式的另一个版本:

      import os
      import re
      
      ipv4 = re.search(re.compile(r'(?<=inet )(.*)(?=\/)', re.M), os.popen('ip addr show eth0').read()).groups()[0]
      ipv6 = re.search(re.compile(r'(?<=inet6 )(.*)(?=\/)', re.M), os.popen('ip addr show eth0').read()).groups()[0]
      

      【讨论】:

      • 很好地使用了ip addr,但是,确实没有必要将输出通过管道传输到 grep 和 awk。 Python 不仅能够使用 split() 解析输出
      • 为了让它更容易被机器解析,试试-oneline选项。 "output each record on a single line, replacing line feeds with the '\' character. This is convenient when you want to count records with wc(1) or to grep(1) the output."
      • 此外,iproute2 的较新版本(其中ip 是其中的一部分)支持-j, --json 参数,用于执行您所期望的大多数命令。
      【解决方案8】:

      返回带有接口 IP 地址的字符串的简单方法是:

      from subprocess import check_output
      
      ips = check_output(['hostname', '--all-ip-addresses'])
      

      有关详细信息,请参阅 hostname

      【讨论】:

      • 我特别喜欢这个,因为它在容器内部时确实提供了 docker 容器的正确 IP。否则会选择第一个被报告的。太棒了!
      • 这是特定于hostname 的 Linux 网络工具版本的。 Mac/BSD 主机名命令不提供此功能。如果您使用的是 Linux 版本,这可能是最简单的方法。
      【解决方案9】:

      answer from jeremyjjbrown 的基础上,这是另一个版本,如 cmets 对他的回答所述,它会自行清理。

      此版本还允许提供不同的服务器地址以用于私有内部网络等。

      import socket
      
      def get_my_ip_address(remote_server="google.com"):
          """
          Return the/a network-facing IP number for this system.
          """
          with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
              s.connect((remote_server, 80))
              return s.getsockname()[0]
      

      【讨论】:

      • 我建议使用 IP 地址而不是主机名作为“remote_server”的默认值,这样就不会触发 DNS 查找。
      【解决方案10】:

      这是 ifconfig 的结果:

      ifconfig
      
      eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
              inet 192.168.2.24  netmask 255.255.255.0  broadcast 192.168.2.255
              inet6 fe80::88e9:4d2:c057:2d5f  prefixlen 64  scopeid 0x20<link>
              ether b8:27:eb:d0:9a:f3  txqueuelen 1000  (Ethernet)
              RX packets 261861  bytes 250818555 (239.1 MiB)
              RX errors 0  dropped 6  overruns 0  frame 0
              TX packets 299436  bytes 280053853 (267.0 MiB)
              TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
      
      lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
              inet 127.0.0.1  netmask 255.0.0.0
              inet6 ::1  prefixlen 128  scopeid 0x10<host>
              loop  txqueuelen 1000  (Local Loopback)
              RX packets 74  bytes 16073 (15.6 KiB)
              RX errors 0  dropped 0  overruns 0  frame 0
              TX packets 74  bytes 16073 (15.6 KiB)
              TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
      
      wlan0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
              ether b8:27:eb:85:cf:a6  txqueuelen 1000  (Ethernet)
              RX packets 0  bytes 0 (0.0 B)
              RX errors 0  dropped 0  overruns 0  frame 0
              TX packets 0  bytes 0 (0.0 B)
              TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
      

      削减一点输出,我们有:

      ifconfig eth0 | grep "inet 192" | cut -c 14-25
      
      192.168.2.24
      

      现在,我们可以去 Python 做:

      import os
      mine = os.popen('ifconfig eth0 | grep "inet 192" | cut -c 14-25')
      myip = mine.read()
      print (myip)
      

      【讨论】:

        【解决方案11】:

        使用 Python 从NIC 获取 IP 地址的另一种方法。

        这是我很久以前开发的应用程序的一部分,我不想简单地git rm script.py。因此,我在这里提供了方法,使用subprocess 和列表推导,以实现函数式方法和更少的代码行:

        import subprocess as sp
        
        __version__ = "v1.0"
        __author__ = "@ivanleoncz"
        
        def get_nic_ipv4(nic):
            """
                Get IP address from a NIC.
        
                Parameter
                ---------
                nic : str
                    Network Interface Card used for the query.
        
                Returns
                -------
                ipaddr : str
                    Ipaddress from the NIC provided as parameter.
            """
            result = None
            try:
                result = sp.check_output(["ip", "-4", "addr", "show", nic],
                                                          stderr=sp.STDOUT)
            except Exception:
                return "Unkown NIC: %s" % nic
            result = result.decode().splitlines()
            ipaddr = [l.split()[1].split('/')[0] for l in result if "inet" in l]
            return ipaddr[0]
        

        此外,您可以使用类似的方法来获取 NIC 列表:

        def get_nics():
            """
                Get all NICs from the Operating System.
        
                Returns
                -------
                nics : list
                    All Network Interface Cards.
            """
            result = sp.check_output(["ip", "addr", "show"])
            result = result.decode().splitlines()
            nics = [l.split()[1].strip(':') for l in result if l[0].isdigit()]
            return nics
        

        这是Gist 的解决方案。

        你会得到这样的东西:

        python3
        
        Python 3.6.7 (default, Oct 22 2018, 11:32:17)
        [GCC 8.2.0] on linux
        Type "help", "copyright", "credits" or "license" for more information.
        >>>
        >>>
        >>> import helpers
        >>>
        >>> helpers.get_nics()
        ['lo', 'enp1s0', 'wlp2s0', 'docker0']
        >>> helpers.get_nic_ipv4('docker0')
        '172.17.0.1'
        >>> helpers.get_nic_ipv4('docker2')
        'Unkown NIC: docker2'
        

        【讨论】:

          【解决方案12】:

          这将收集主机上的所有 IP 地址并过滤掉 loopback/link-local 和 IPv6。这也可以编辑为仅允许 IPv6,或同时允许 IPv4 和 IPv6,以及允许 IP 地址列表中的环回/链接本地。

          from socket import getaddrinfo, gethostname
          import ipaddress
          
          def get_ip(ip_addr_proto="ipv4", ignore_local_ips=True):
              # By default, this method only returns non-local IPv4 addresses
              # To return IPv6 only, call get_ip('ipv6')
              # To return both IPv4 and IPv6, call get_ip('both')
              # To return local IPs, call get_ip(None, False)
              # Can combine options like so get_ip('both', False)
          
              af_inet = 2
              if ip_addr_proto == "ipv6":
                  af_inet = 30
              elif ip_addr_proto == "both":
                  af_inet = 0
          
              system_ip_list = getaddrinfo(gethostname(), None, af_inet, 1, 0)
              ip_list = []
          
              for ip in system_ip_list:
                  ip = ip[4][0]
          
                  try:
                      ipaddress.ip_address(str(ip))
                      ip_address_valid = True
                  except ValueError:
                      ip_address_valid = False
                  else:
                      if ipaddress.ip_address(ip).is_loopback and ignore_local_ips or ipaddress.ip_address(ip).is_link_local and ignore_local_ips:
                          pass
                      elif ip_address_valid:
                          ip_list.append(ip)
          
              return ip_list
          
          print(f"Your IP address is: {get_ip()}")
          

          返回

          您的 IP 地址是:['192.168.1.118']

          如果我运行 get_ip('both', False),它会返回

          您的 IP 地址是:['::1', 'fe80::1', '127.0.0.1', '192.168.1.118', 'fe80::cb9:d2dd:a505:423a']

          【讨论】:

            【解决方案13】:

            你可以试试这段代码sn-p:

            import netifaces as ni
            
            def test_network():
                interfaces = ni.interfaces()
            
                for i in interfaces: #Will cycle through all available interfaces and check each one.
                    if i != "lo": #This will remove lo from the interfaces it checks.
                        try:
                            ni.ifaddresses(i)
                            gws = ni.gateways()
                            gateway = gws['default'][ni.AF_INET][0]
                            ip = ni.ifaddresses(i)[ni.AF_INET][0]['addr']
                            sm = ni.ifaddresses(i)[ni.AF_INET][0]['netmask']
                            print ("Network information for " + i + ":")
                            print ("IP address: " + ip)
                            print ("Subnet Mask: " + sm)
                            print ("Gateway: " + gateway)
                            print ()
                        except: #Error case for a disconnected Wi-Fi or trying to test a network with no DHCP
                            print (i + " is not connected or DHCP is not available. Try setting a static IP address.")
            test_network()
            

            这可能会产生以下结果:

            Network information for eth0:
            
            IP address: 192.168.1.172
            
            Subnet Mask: 255.255.255.0
            
            Gateway: 192.168.1.254
            
            wlan0 is not connected or DHCP is not available.
            

            【讨论】:

              【解决方案14】:

              如果你想用硬(但也许很快?)的方式来做,这里有一些粗略的Netlink (RFC 3549) 代码(可能仅限 Linux),它同时获取 IPv4 和 IPv6,只有一个 import 语句来自标准库:

                  import socket
                  # https://www.man7.org/linux/man-pages/man7/rtnetlink.7.html
                  # https://github.com/torvalds/linux/blob/master/include/uapi/linux/rtnetlink.h
                  RTM_NEWADDR = 20
                  RTM_GETADDR = 22
                  # https://www.man7.org/linux/man-pages/man7/netlink.7.html
                  # https://github.com/torvalds/linux/blob/master/include/uapi/linux/netlink.h
                  NLM_F_REQUEST = 0x01
                  NLM_F_ROOT = 0x100
                  s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW)
                  req = (
                      # nlmsghdr
                      int.to_bytes(0, 4, 'little', signed=False) +  # nlmsg_len
                      int.to_bytes(RTM_GETADDR, 2, 'little', signed=False) +  # nlmsg_type
                      int.to_bytes(NLM_F_REQUEST | NLM_F_ROOT, 2, 'little', signed=False) +  # nlmsg_flags
                      int.to_bytes(0, 2, 'little', signed=False) +  # nlmsg_seq
                      int.to_bytes(0, 2, 'little', signed=False) +  # nlmsg_pid
                      # ifinfomsg
                      b'\0' * 8
                  )
                  req = int.to_bytes(len(req), 4, 'little') + req[4:]
                  s.sendall(req)
                  full_resp = s.recv(4096)
                  while full_resp:
                      resp = full_resp
                      # nlmsghdr
                      nlmsg_len = int.from_bytes(resp[0:4], 'little', signed=False)
                      nlmsg_type = int.from_bytes(resp[4:6], 'little', signed=False)
                      assert not nlmsg_len % 4, nlmsg_len
                      resp = resp[16:nlmsg_len]
                      full_resp = full_resp[nlmsg_len:]
                      if nlmsg_type == 3:  # NLMSG_DONE
                          assert not full_resp, full_resp
                          break
                      if not full_resp:
                          full_resp = s.recv(4096)
                      assert nlmsg_type == RTM_NEWADDR, (nlmsg_type, resp[:32])
                      # ifaddrmsg
                      ifa_family = int.from_bytes(resp[0:1], 'little', signed=False)
                      ifa_index = int.from_bytes(resp[4:8], 'little', signed=False)
                      resp = resp[8:]
                      while resp:
                          # rtattr
                          rta_len = int.from_bytes(resp[0:2], 'little', signed=False)
                          rta_type = int.from_bytes(resp[2:4], 'little', signed=False)
                          data = resp[4:rta_len]
                          if rta_type == 1:  # IFLA_ADDRESS
                              if ifa_family == socket.AF_INET:
                                  ip = '.'.join('%d' % c for c in data)
                              if ifa_family == socket.AF_INET6:
                                  ip = ':'.join(('%02x%02x' % (chunk[0], chunk[1]) if chunk != b'\0\0' else '') for chunk in [data[0:2], data[2:4], data[4:6], data[6:8], data[8:10], data[10:12], data[12:14], data[14:16]])
                              print('interface #%s has %s' % (ifa_index, ip))
                          if rta_type == 3:  # IFLA_IFNAME
                              ifname = data.rstrip(b'\0').decode()
                              print('interface #%s is named %s' % (ifa_index, ifname))
                          # need to round up to multiple of 4
                          if rta_len % 4:
                              rta_len += 4 - rta_len % 4
                          resp = resp[rta_len:]
                  s.close()
              

              如果您只需要 IPv4,另一个答案中的老派 SIOCGIFADDR ioctl 方法可能更直接。对于 IPv6,有 /proc/net/if_inet6

              【讨论】:

                猜你喜欢
                • 2011-11-09
                • 2020-07-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-04-08
                • 1970-01-01
                • 2011-09-08
                相关资源
                最近更新 更多