Home > front end >  Get IP address of virtual network interface
Get IP address of virtual network interface

Time:01-06

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:

  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<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 on lo 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:

  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.

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.

  • Related