簡體   English   中英

如何從 Python 中的 NIC(網絡接口控制器)獲取 IP 地址?

[英]How can I get the IP address from a NIC (network interface controller) in Python?

當 Unix 上的 Python 腳本發生錯誤時,將發送 email。

如果 IP 地址是測試服務器 192.168.100.37,我被要求將 {Testing Environment} 添加到 email 的主題行。 這樣我們就可以擁有一個腳本版本和一種方法來判斷 email 是否來自測試服務器上的混亂數據。

然而,當我用谷歌搜索時,我一直在尋找這段代碼:

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地址呢?

兩種方法:

方法#1(使用外部包)

您需要詢問綁定到eth0接口的 IP 地址。 這可以從netifaces 包中獲得

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

或者,如果您想獲取用於連接到網絡的任何接口的 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 的路由來使用它。 它所做的只是打開一個套接字,但不發送任何數據。

返回帶有接口 ip 地址的字符串的簡單方法是:

from subprocess import check_output

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

有關更多信息,請參閱主機名

由於大多數答案使用ifconfig從 eth0 接口提取 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()而不是 grep 和 awk 將部分解析任務轉移到 python 解釋器,正如@serg 在評論中指出的那樣:

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]

如果您只需要在 Unix 上工作,您可以使用系統調用(參考 Stack Overflow question 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()

以@jeremyjjbrown 的答案為基礎,這是另一個版本,如對他的答案的評論中所述,會自行清理。 此版本還允許提供不同的服務器地址以用於私有內部網絡等。

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]

試試下面的代碼,它在 Mac10.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

使用 Python 從 NIC 獲取 IP 地址的另一種方法。

我把它作為我很久以前開發的應用程序的一部分,我不想簡單地git rm script.py 所以,在這里我提供了一種方法,為了功能方法和更少的代碼行,使用subprocess和列表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'

這將收集主機上的所有 IP 並過濾掉環回/本地鏈路和 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 combime 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']

它對我有用

 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

在正在運行的 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

這是 ifconfig 的結果:

pi@raspberrypi:~ $ 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

pi@raspberrypi:~ $ 

減少一點輸出,我們有:

pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ ifconfig eth0 | grep "inet 192" | cut -c 14-25
192.168.2.24
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ 

現在,我們可以轉到 python 並執行以下操作:

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

如果你想用硬(但可能很快?)的方式來做,這里有一些粗略的 netlink (rfc3549) 代碼(可能僅限於 linux),它同時獲取 ipv4 和 ipv6,只有一個來自標准庫的導入語句:

    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,另一個答案中的 oldschool SIOCGIFADDR ioctl方法可能更簡單。 對於 ipv6,有/proc/net/if_inet6

使用psutil回答:

import psutil
import socket

def get_ipv4_from_nic(interface):
    interface_addrs = psutil.net_if_addrs().get(interface) or []
    for snicaddr in interface_addrs:
        if snicaddr.family == socket.AF_INET:
            return snicaddr.addressinterface_addrs]

例子:

>>> get_ipv4_from_nic("eth0")
'192.168.100.37'

嘗試這個:

將 netifaces 導入為 ni

def test_netowrk(): interfaces = ni.interfaces()

for i in interfaces: #will cycle through all available interfaces and check each one. you can use anything for "i". its almost like saying import netifaces as ni. but i doesn't need to be declared, the systems assumes that for (what you put here) in interfaces means what you put there should resemble each of the results returned are represented by i.
    if i != "lo": #lo is the local loopback. 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: " + ip)
            print ("Subnet Mask: " + sm)
            print ("Gateway: " + gateway)
            print ()
        except: #this is good to have in case you have a disconnected wifi or trying to test a network with no DHCP
            print (i + " is not connected or DHCP is not available. Try setting a static IP.")

測試網絡()

結果:

eth0 的網絡信息:

IP:192.168.1.172

子網掩碼:255.255.255.0

網關:192.168.1.254

wlan0 未連接或 DHCP 不可用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM