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: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
.
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.