用Python获取本机IP地址

Last Updated: 2023-05-12 01:05:04 Friday

-- TOC --

用Python获取本机IP地址,其实还是个技术活,有点小技巧。

注意:获取本机IP地址,与获取公网IP地址,不一样,你的公网IP,很可能是你的路由器WAN口获取的IP地址。

不能使用gethostbyname

用gethostbyname,得到的是一个127开头的还回地址:

>>> import socket
>>> hn = socket.gethostname()
>>> hn
'rp02'
>>> socket.gethostbyname('rp02')
'127.0.1.1'

本机有3个IP地址,两个公网一个还回,gethostbyname偏偏返回了那个不可用的还回地址,难道是因为这个地址的编号是1吗?

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether dc:a6:32:75:8f:38 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.120/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0
       valid_lft 5061sec preferred_lft 3911sec
    inet6 fe80::c561:5c79:3a3:5894/64 scope link
       valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether dc:a6:32:75:8f:39 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.107/24 brd 192.168.2.255 scope global dynamic noprefixroute wlan0
       valid_lft 6481sec preferred_lft 5581sec
    inet6 fe80::a807:50c7:dc55:4688/64 scope link
       valid_lft forever preferred_lft forever

gethostbyname_ex也不好用

>>> socket.gethostbyname_ex('rp02')
('rp02', [], ['127.0.1.1'])

这个函数在Windows平台下,会返回一个IP地址list:

>>> socket.gethostname()
'DESKTOP-LKS6V4S'
>>> socket.gethostbyname_ex('DESKTOP-LKS6V4S')
('DESKTOP-LKS6V4S', [], ['192.168.191.1', '192.168.125.1', '192.168.2.102'])

还是不好用!

用UDP socket获取公网IP

这是一个迂回的方法,但是很好用:

def get_network_ip():
    """get the network ip, not loopback addr"""
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(('8.8.8.8',80))
    ip = s.getsockname()[0]
    s.close()
    return ip

此函数创建一个UDP socket,然后是用connect函数固定对端通信地址,OS底层会去查路由表,使用一个能够到达对端IP地址,作为此UDP socket绑定的IP地址。8.8.8.8是Google提供的DNS服务地址!

>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.connect(('8.8.8.8',80))
>>> s.getsockname()
('192.168.1.120', 46396)

用这个方法获取本机公网IP地址,存在一个隐患,connect函数会触发OS去查路由表,在本机系统启动过程中,如果路由表项的创建在此查询之后,就会触发异常,代码抛出对端地址不可达的异常(OSError,network unreachable)。要解决这个问题也很简单,要么确保此代码在路由表项创建之后运行,要么在失败之后等待一个时间,再次尝试。

关于UDP socket使用connect函数的详细说明,请参考:详解Python的UDP编程

使用ioctl

此方法只适合Linux系统,Windows系统下的python,没有fcntl模块。

>>> import socket
>>> from fcntl import ioctl
>>> import struct
>>>
>>> s = socket.socket(socket.AF_INET, SOCK_DGRAM)
>>> ip_n = fcntl.ioctl(s.fileno(),0x8915,struct.pack('256s',b'eth0'))[20:24]
>>> ip_n
b'\xc0\xa8\x02g'
>>> socket.inet_ntoa(ip_n)
'192.168.2.103'

这个方法需要知道网卡名称,比如上述示例使用了eth0,也有一定局限性。查看系统所有网卡,ifconfig,或者ip a

本文链接:https://cs.pynote.net/sf/python/202109265/

-- EOF --

-- MORE --