如何使用 Python 从网卡(网卡)取得 IP 地址?

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

如果 IP 地址是192.168.100.37(测试服务器) ,我被要求在电子邮件的主题行添加{ TestingEnvironment }。通过这种方式,我们可以有一个脚本版本和一种方法来判断电子邮件是否来自测试服务器上混乱的数据。

然而,当我谷歌我不断发现这个代码:

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

然而,这给我的 IP 地址是127.0.1.1。当我使用 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的,但不管怎样,这不是我想要的。当我谷歌我一直来到相同的语法,巴斯脚本或网络面,我试图使用标准库。

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

241171 次浏览

Two methods:

Method #1 (use external package)

You need to ask for the IP address that is bound to your eth0 interface. This is available from the netifaces package

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

You can also get a list of all available interfaces via

ni.interfaces()

Method #2 (no external package)

Here's a way to get the IP address without using a python package:

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'

Note: detecting the IP address to determine what environment you are using is quite a hack. Almost all frameworks provide a very simple way to set/modify an environment variable to indicate the current environment. Try and take a look at your documentation for this. It should be as simple as doing

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

Alternatively, if you want to get the IP address of whichever interface is used to connect to the network without having to know its name, you can use this:

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]

I know it's a little different than your question, but others may arrive here and find this one more useful. You do not have to have a route to 8.8.8.8 to use this. All it is doing is opening a socket, but not sending any data.

If you only need to work on Unix, you can use a system call (ref. 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()

Try the below code. It works for me in Mac OS X v10.10.2 (Yosemite):

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

It worked for me

 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

Find the IP address of the first eth/wlan entry in ifconfig that's running:

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

Since most of the answers use ifconfig to extract the IPv4 address from the eth0 interface, which is deprecated on most Linux distros in favor of ip addr, the following code could be used instead:

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()

Alternatively, you can shift part of the parsing task to the Python interpreter by using split() instead of grep and AWK, as Sergiy Kolodyazhnyy points out in the comment:

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]

But in this case you have to check the bounds of the array returned by each split() call.


Another version using regex:

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]

A simple approach which returns a string with IP addresses for the interfaces is:

from subprocess import check_output


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

For more information, see hostname.

Building on the answer from jeremyjjbrown, here is another version that cleans up after itself as mentioned in the comments to his answer.

This version also allows providing a different server address for use on private internal networks, etc.

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]

This is the result of 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

Cutting a bit the output, we have:

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


192.168.2.24

Now, we can go to Python and do:

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

Yet another way of obtaining the IP address from a NIC, using Python.

I had this as part of an application that I developed long time ago, and I didn't wanted to simply git rm script.py. So, here I provide the approach, using subprocess and list comprehensions for the sake of functional approach and fewer lines of code:

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]

Additionally, you can use a similar approach for obtaining a list of NICs:

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

Here's the solution as a Gist.

And you would have something like this:

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'

This will gather all IP addresses on the host and filter out loopback/link-local and IPv6. This can also be edited to allow for IPv6 only, or both IPv4 and IPv6, as well as allowing loopback/link-local in the IP address list.

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()}")

Returns

Your IP address is: ['192.168.1.118']

If I run get_ip('both', False), it returns

Your IP address is: ['::1', 'fe80::1', '127.0.0.1', '192.168.1.118', 'fe80::cb9:d2dd:a505:423a']

You can try this code snippet:

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()

This may produce the following result:

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.

If you want to do it the hard (but maybe fast?) way, here's some rough Netlink (RFC 3549) code (probably Linux only) which gets both IPv4 and IPv6, with just one import statement from the standard library:

    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()

If you just need IPv4, the old school SIOCGIFADDR ioctl method in another answer is probably more straightforward. For IPv6, there's /proc/net/if_inet6.

Answer using 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]

Example:

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