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.
CodePudding user response:
I think I spot two immediate problems with your code:
An interface alias like
lo:0isn't an "virtual interface", it's just a label applied to an address. You'll find the address associated with the main interface (loin this case). The output of theifconfigcommand is misleading and you shouldn't be using it; useip addrto see you interface address configuration.For example, if I create an "alias interface" with
ifconfig, like this:# ifconfig lo:0 192.168.123.123/24I 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 addrshows 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 foreverThere you can see that the secondary address is actually associated with device
lowith some extra metadata. If we ask for the interface address onloin 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 addrand parse the output. You can get JSON output by callingip -j addr show.
CodePudding user response:
On Linux you could use https://github.com/vishvananda/netlink to get the IP addresses and labels.
This could look something like this:
package main
import (
"errors"
"fmt"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"net"
"syscall"
)
func main() {
interfaceName := "lo:0"
ip, err := GetInterfaceIpAddr(interfaceName)
if err != nil {
panic(err)
}
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 "", errors.New("not found")
}
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 = net.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.
