Получить IP-адрес виртуального сетевого интерфейса

Как я могу получить 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 { // 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
}

Когда я передаю lo:0 выше, net.InterfaceByName завершается с ошибкой: route ip+net: no such network interface.

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
0
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, что заметил две непосредственные проблемы с вашим кодом:

  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 с некоторыми дополнительными метаданными. Если мы запросим адрес интерфейса на lo в Go:

    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, но (а) ваш код возвращает только один адрес и (б) ваш код всегда возвращает только первый адрес.

Подходящее решение здесь зависит от того, что вы пытаетесь сделать:

  • Просто передайте коду явный адрес, а не пытайтесь найти его по имени интерфейса. Так как интерфейс может иметь несколько адресов — и на самом деле это довольно распространенное явление — на самом деле не существует хорошего способа определить «адрес интерфейса».

    Думаю, в большинстве случаев это будет лучший вариант.

  • Найдите (или напишите) код, который может получить все метаданные, связанные с интерфейсом ввода, чтобы вы могли искать конкретный label.

  • Просто вызовите ip addr и проанализируйте вывод. Вы можете получить вывод JSON, позвонив ip -j addr show.

Решение № 2 было лучшим вариантом для нас. Решение №1 изменит способ развертывания.

user2233706 06.01.2023 20:44
Ответ принят как подходящий

В 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

Это, вероятно, еще не то, что вам нужно в деталях. Но это может быть первым шагом в правильном направлении.

Удивительный ответ. Именно то, что я хотел.

user2233706 06.01.2023 20:43

Другие вопросы по теме