英文:
Get IP address of virtual network interface
问题
如何获取虚拟网络接口的IP地址?这是一个类似于以下内容的接口:
lo:0: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 192.168.40.1 netmask 255.255.255.255
loop txqueuelen 1000 (Local Loopback)
这是我获取常规接口的IP地址的方法:
func GetInterfaceIpAddr(interfaceName string) (string, error) {
var (
ief *net.Interface
addrs []net.Addr
ipv4Addr net.IP
)
ief, err := net.InterfaceByName(interfaceName)
if err != nil { // 获取接口
log.Info("InterfaceByName failed")
return "", err
}
addrs, err = ief.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs { // 获取IPv4地址
if ipv4Addr = addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
break
}
}
if ipv4Addr == nil {
return "", errors.New(fmt.Sprintf("interface %s doesn't have an ipv4 address\n", interfaceName))
}
return ipv4Addr.String(), nil
}
当我将lo:0
传递给上述函数时,net.InterfaceByName
会失败,并显示以下错误:route ip+net: no such network interface
。
英文:
How can I get the IP address of a virtual network interface? This is an interface that looks like this:
lo:0: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 192.168.40.1 netmask 255.255.255.255
loop txqueuelen 1000 (Local Loopback)
This is how I retrieve the IP address of a regular interface:
func GetInterfaceIpAddr(interfaceName string) (string, error) {
var (
ief *net.Interface
addrs []net.Addr
ipv4Addr net.IP
)
ief, err := net.InterfaceByName(interfaceName)
if err != nil { // get interface
log.Info("InterfaceByName failed")
return "", err
}
addrs, err = ief.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs { // get ipv4 address
if ipv4Addr = addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
break
}
}
if ipv4Addr == nil {
return "", errors.New(fmt.Sprintf("interface %s doesn't have an ipv4 address\n", interfaceName))
}
return ipv4Addr.String(), nil
}
When I pass lo:0
to the above, net.InterfaceByName
fails with this error: route ip+net: no such network interface
.
答案1
得分: 1
我认为你的代码存在两个问题:
-
类似
lo:0
这样的接口别名并不是一个"虚拟接口",它只是一个应用于地址的标签。你应该找到与主接口关联的地址(在这种情况下是lo
)。ifconfig
命令的输出是误导性的,你不应该使用它;应该使用ip addr
命令来查看接口地址配置。例如,如果我使用
ifconfig
创建一个"别名接口",像这样:# ifconfig lo:0 192.168.123.123/24
我会在
ifconfig
的输出中看到以下内容:# ifconfig 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 504291 bytes 72010889 (68.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 504291 bytes 72010889 (68.6 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo:0: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 192.168.123.123 netmask 255.255.255.0 loop txqueuelen 1000 (Local Loopback)
而
ip addr
命令显示的是:# ip addr show lo 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 inet 192.168.123.123/24 scope global lo:0 valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
在这里,你可以看到次要地址实际上与设备
lo
关联,并带有一些额外的元数据。如果我们在Go中请求lo
接口的地址:ief, _ := net.InterfaceByName("lo") addrs, _ := ief.Addrs() fmt.Printf("addrs: %v\n", addrs)
我们会得到:
addrs: [127.0.0.1/8 192.168.123.123/24 ::1/128]
所以对于你问题的第一部分,答案是"使用主接口名称"。但是还有第二个问题:
-
接口可以有多个IPv4地址,但是(a)你的代码只返回一个地址,(b)你的代码只会返回第一个地址。
这里适当的解决方案取决于你想要做什么:
-
直接向你的代码传递一个明确的地址,而不是尝试从接口名称中获取它。由于一个接口可以有多个地址,事实上这是相对常见的,所以没有一个很好的方法来确定"接口的地址"。
我认为在大多数情况下,这将是最好的选择。
-
找到(或编写)可以获取与接口关联的所有元数据的代码,这样你就可以查找特定的
label
。 -
直接调用
ip addr
并解析输出。你可以通过调用ip -j addr show
来获取JSON输出。
-
英文:
I think I spot two immediate problems with your code:
-
An interface alias like
lo:0
isn't an "virtual interface", it's just a label applied to an address. You'll find the address associated with the main interface (lo
in this case). The output of theifconfig
command is misleading and you shouldn't be using it; useip addr
to see you interface address configuration.For example, if I create an "alias interface" with
ifconfig
, like this:# ifconfig lo:0 192.168.123.123/24
I see the following output from
ifconfig
:# ifconfig 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 504291 bytes 72010889 (68.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 504291 bytes 72010889 (68.6 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo:0: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 192.168.123.123 netmask 255.255.255.0 loop txqueuelen 1000 (Local Loopback)
Whereas
ip addr
shows me:# ip addr show lo 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 inet 192.168.123.123/24 scope global lo:0 valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
There you can see that the secondary address is actually associated with device
lo
with some extra metadata. If we ask for the interface address onlo
in Go:ief, _ := net.InterfaceByName("lo") addrs, _ := ief.Addrs() fmt.Printf("addrs: %v\n", addrs)
We get:
addrs: [127.0.0.1/8 192.168.123.123/24 ::1/128]
So the answer to the first part of your question is, "use the primary interface name". But there's a second problem:
-
Interfaces can have more than one ipv4 address but (a) your code only returns a single address and (b) your code will only ever return the first address.
The appropriate solution here depends on what you're trying to do:
-
Just pass your code an explicit address rather than trying to discover it from the interface name. Since an interface can have multiple addresses -- and in fact that's relatively common -- there's not really a great way to determine "the address of an interface".
I think in most cases this will be the best option.
-
Find (or write) code that can fetch all the metadata associated with an in interface, so that you can look for a specific
label
. -
Just call
ip addr
and parse the output. You can get JSON output by callingip -j addr show
.
答案2
得分: 0
在Linux上,你可以使用https://github.com/vishvananda/netlink来获取IP地址和标签。
这是一个允许Linux用户空间程序通过netlink接口与内核通信的库。它可以用于配置接口和路由等,或者检索信息。
在Linux 2.0中,有一个别名的概念,当执行ifconfig时,通过在接口名后添加冒号和字符串来形成别名。自从Linux 2.2引入了在接口上有多个地址的可能性后,这个概念已经被弃用,参见https://www.kernel.org/doc/Documentation/networking/alias.txt
然而,它是向后兼容的,通过Netlink接口,你可以检索包括IFA_LABEL在内的属性,它表示接口名(包括别名)。
由于问题明确涉及到别名,假设每个接口名配置了一个IP地址。然而,如果需要,可以很容易地添加返回每个接口的多个IP地址,因为当然会返回完整的NetlinkRouteAttr
数据。这样就可以同时涵盖两种情况。
代码示例如下:
package main
import (
"fmt"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"net"
"syscall"
)
func main() {
interfaceName := "lo:0"
ip, err := GetInterfaceIpAddr(interfaceName)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s -> %s\n", interfaceName, ip)
}
func GetInterfaceIpAddr(interfaceName string) (string, error) {
ifis, err := interfaces(netlink.FAMILY_V4)
if err != nil {
return "", err
}
ip, ok := ifis[interfaceName]
if !ok {
return "", fmt.Errorf("%s not found", interfaceName)
}
return ip.String(), nil
}
func interfaces(family int) (map[string]net.IP, error) {
req := nl.NewNetlinkRequest(syscall.RTM_GETADDR, syscall.NLM_F_DUMP)
msg := nl.NewIfInfomsg(family)
req.AddData(msg)
messages, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWADDR)
if err != nil {
return nil, err
}
ifis := make(map[string]net.IP)
for _, m := range messages {
msg := nl.DeserializeIfAddrmsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
var ip net.IP
var label string
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.IFA_LOCAL:
ip = attr.Value
case syscall.IFA_LABEL:
label = string(attr.Value[:len(attr.Value)-1])
}
}
if ip != nil && label != "" {
ifis[label] = ip
}
}
return ifis, nil
}
在Ubuntu上的测试结果为:
lo:0 -> 127.0.0.2
这可能还不是你需要的详细信息,但它可以是朝着正确方向迈出的第一步。
英文:
On Linux you could use https://github.com/vishvananda/netlink to get the IP addresses and labels.
This is a library that allows a Linux user-space program to communicate with the kernel via the netlink interface. It can be used to configure interfaces and routes, etc. or to retrieve the informations.
In Linux 2.0 there was the alias concept, which was formed by adding a colon and a string to the interface name when executing ifconfig. Since Linux 2.2, which introduced the possibility of having multiple addresses per interface, this concept has been deprecated, see https://www.kernel.org/doc/Documentation/networking/alias.txt
Nevertheless, it is backward compatible, and via the Netlink interface you can retrieve the attributes including IFA_LABEL, which represent interface names (including alias names).
Since the question explicitly refers to the aliases, it is assumed that one IP address per interface name is configured. If necessary, however, it could easily be added to return multiple IP addresses per interface, since of course the full NetlinkRouteAttr
data is returned. This would then cover both cases.
This could look something like this:
package main
import (
"fmt"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"net"
"syscall"
)
func main() {
interfaceName := "lo:0"
ip, err := GetInterfaceIpAddr(interfaceName)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s -> %s\n", interfaceName, ip)
}
func GetInterfaceIpAddr(interfaceName string) (string, error) {
ifis, err := interfaces(netlink.FAMILY_V4)
if err != nil {
return "", err
}
ip, ok := ifis[interfaceName]
if !ok {
return "", fmt.Errorf("%s not found", interfaceName)
}
return ip.String(), nil
}
func interfaces(family int) (map[string]net.IP, error) {
req := nl.NewNetlinkRequest(syscall.RTM_GETADDR, syscall.NLM_F_DUMP)
msg := nl.NewIfInfomsg(family)
req.AddData(msg)
messages, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWADDR)
if err != nil {
return nil, err
}
ifis := make(map[string]net.IP)
for _, m := range messages {
msg := nl.DeserializeIfAddrmsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
var ip net.IP
var label string
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.IFA_LOCAL:
ip = attr.Value
case syscall.IFA_LABEL:
label = string(attr.Value[:len(attr.Value)-1])
}
}
if ip != nil && label != "" {
ifis[label] = ip
}
}
return ifis, nil
}
A test on Ubuntu gives:
lo:0 -> 127.0.0.2
This is probably not yet what you need in detail. But it could be a first step in the right direction.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论