获取虚拟网络接口的IP地址

huangapple go评论79阅读模式
英文:

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&lt;UP,LOOPBACK,RUNNING&gt;  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(&quot;InterfaceByName failed&quot;)
		return &quot;&quot;, err
	}

	addrs, err = ief.Addrs()

	if err != nil {
		return &quot;&quot;, err
	}

	for _, addr := range addrs { // get ipv4 address
		if ipv4Addr = addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
			break
		}
	}
	if ipv4Addr == nil {
		return &quot;&quot;, errors.New(fmt.Sprintf(&quot;interface %s doesn&#39;t have an ipv4 address\n&quot;, 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

我认为你的代码存在两个问题:

  1. 类似lo:0这样的接口别名并不是一个"虚拟接口",它只是一个应用于地址的标签。你应该找到与主接口关联的地址(在这种情况下是lo)。ifconfig命令的输出是误导性的,你不应该使用它;应该使用ip addr命令来查看接口地址配置。

    例如,如果我使用ifconfig创建一个"别名接口",像这样:

    # ifconfig lo:0 192.168.123.123/24
    

    我会在ifconfig的输出中看到以下内容:

    # ifconfig
    lo: flags=73&lt;UP,LOOPBACK,RUNNING&gt;  mtu 65536
            inet 127.0.0.1  netmask 255.0.0.0
            inet6 ::1  prefixlen 128  scopeid 0x10&lt;host&gt;
            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&lt;UP,LOOPBACK,RUNNING&gt;  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: &lt;LOOPBACK,UP,LOWER_UP&gt; 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]
    

    所以对于你问题的第一部分,答案是"使用主接口名称"。但是还有第二个问题:

  2. 接口可以有多个IPv4地址,但是(a)你的代码只返回一个地址,(b)你的代码只会返回第一个地址。

    这里适当的解决方案取决于你想要做什么:

    • 直接向你的代码传递一个明确的地址,而不是尝试从接口名称中获取它。由于一个接口可以有多个地址,事实上这是相对常见的,所以没有一个很好的方法来确定"接口的地址"。

      我认为在大多数情况下,这将是最好的选择。

    • 找到(或编写)可以获取与接口关联的所有元数据的代码,这样你就可以查找特定的label

    • 直接调用ip addr并解析输出。你可以通过调用ip -j addr show来获取JSON输出。

英文:

I think I spot two immediate problems with your code:

  1. 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 the ifconfig command is misleading and you shouldn't be using it; use ip 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&lt;UP,LOOPBACK,RUNNING&gt;  mtu 65536
            inet 127.0.0.1  netmask 255.0.0.0
            inet6 ::1  prefixlen 128  scopeid 0x10&lt;host&gt;
            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&lt;UP,LOOPBACK,RUNNING&gt;  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: &lt;LOOPBACK,UP,LOWER_UP&gt; 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 on lo in Go:

    ief, _ := net.InterfaceByName(&quot;lo&quot;)
    addrs, _ := ief.Addrs()
    fmt.Printf(&quot;addrs: %v\n&quot;, 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:

  2. 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 calling ip -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 (
&quot;fmt&quot;
&quot;github.com/vishvananda/netlink&quot;
&quot;github.com/vishvananda/netlink/nl&quot;
&quot;net&quot;
&quot;syscall&quot;
)
func main() {
interfaceName := &quot;lo:0&quot;
ip, err := GetInterfaceIpAddr(interfaceName)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf(&quot;%s -&gt; %s\n&quot;, interfaceName, ip)
}
func GetInterfaceIpAddr(interfaceName string) (string, error) {
ifis, err := interfaces(netlink.FAMILY_V4)
if err != nil {
return &quot;&quot;, err
}
ip, ok := ifis[interfaceName]
if !ok {
return &quot;&quot;, fmt.Errorf(&quot;%s not found&quot;, 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 &amp;&amp; label != &quot;&quot; {
ifis[label] = ip
}
}
return ifis, nil
}

A test on Ubuntu gives:

lo:0 -&gt; 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.

huangapple
  • 本文由 发表于 2023年1月6日 04:35:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75024076.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定