Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Как я могу получить имя/индекс интерфейса, связанный с сокетом TCP?

Автор вопроса: LeeorОтветов: 9

Я пишу TCP-сервер, которому нужно знать, с какого интерфейса приходит каждое соединение. Я не могу использовать адрес/подсеть, чтобы определить, какой интерфейс использовался, поскольку могут существовать интерфейсы с одинаковыми значениями адреса/подсети. Он основан на Linux, и нет необходимости в переносимости кода.

Все, что я смог найти, это функции для получения всех интерфейсов или одного интерфейса по индексу. Я не смог найти способ получить интерфейс, связанный с принятым сокетом TCP.

Любые идеи? Что-то я пропустил?

РЕДАКТИРОВАТЬ: Повторюсь, IP-адреса в моем случае не уникальны. Ни адреса назначения (сам сервер), ни адреса источника (клиенты). Да, это очень экстремальная схема IP.

  • Единственный правильный ответ здесь, учитывая, что вы ищете код для добавления на свой сервер, - это ответ от User1. Я проверил это, и это работает - вы должны принять это. См. также stackoverflow.com/questions/43659634/ EML 28.04.2017

linuxtcpsockets

Ответы

Ответ 1 · рейтинг 8 · SKi

Используйте getsockname(), чтобы получить IP локального конца соединения TCP. Затем используйте getifaddrs(), чтобы найти соответствующий интерфейс:

struct sockaddr_in addr;
struct ifaddrs* ifaddr;
struct ifaddrs* ifa;
socklen_t addr_len;

addr_len = sizeof (addr);
getsockname(sock_fd, (struct sockaddr*)&addr, &addr_len);
getifaddrs(&ifaddr);

// look which interface contains the wanted IP.
// When found, ifa->ifa_name contains the name of the interface (eth0, eth1, ppp0...)
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
    if (ifa->ifa_addr)
    {
        if (AF_INET == ifa->ifa_addr->sa_family)
        {
            struct sockaddr_in* inaddr = (struct sockaddr_in*)ifa->ifa_addr;

            if (inaddr->sin_addr.s_addr == addr.sin_addr.s_addr)
            {
                if (ifa->ifa_name)
                {
                    // Found it
                }
            }
        }
    }
}
freeifaddrs(ifaddr);

Выше приведен просто грязный пример, необходимы некоторые модификации:

  1. Добавить отсутствующие проверки ошибок
  2. поддержка IPv6

Ответ 2 · рейтинг 4 · Kieron

В общем, вам не нужно знать, через какой интерфейс будут отправляться/получаться пакеты; это работа таблицы маршрутизации ядра. Трудно найти интерфейс для сокета, потому что на самом деле нет прямой связи. Маршрутизация пакетов может меняться в течение времени существования сокета в зависимости от информации о маршрутизации.

Для сокетов дейтаграмм (UDP) вы можете использовать getsockopt(s, IPPROTO_IP, IP_PKTINFO, ...); см. getsockopt(2) и ip(7).

Для потоковых (TCP) сокетов одним из вариантов может быть открытие нескольких прослушивающих сокетов, по одному для каждого интерфейса в системе, и использование setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ...) для привязки каждого к одному интерфейсу; см. setsockopt(2) и socket(7).

Ответ 3 · рейтинг 4 · Waxrat

Вот код C++11 для поиска имени интерфейса сокета:

std::string to_string(sockaddr_in const& addr)
{
    char buf[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf)) == nullptr)
    {
        std::clog << "inet_ntop: " << strerror(errno) << '\n';
        return {};
    }
    return buf;
}

std::string to_string(sockaddr_in6 const& addr)
{
    char buf[INET6_ADDRSTRLEN];
    if (inet_ntop(AF_INET6, &addr.sin6_addr, buf, sizeof(buf)) == nullptr)
    {
        std::clog << "inet_ntop: " << strerror(errno) << '\n';
        return {};
    }
    return buf;
}

std::string to_string(sockaddr_storage const& addr, socklen_t len)
{
    switch (addr.ss_family)
    {
    case AF_INET:
    {
        auto& a = reinterpret_cast<sockaddr_in const&>(addr);
        if (len < sizeof(a))
        {
            std::clog << "Invalid sockaddr length: " << len << '\n';
            return {};
        }
        return to_string(a);
    }
    case AF_INET6:
    {
        auto& a = reinterpret_cast<sockaddr_in6 const&>(addr);
        if (len < sizeof(a))
        {
            std::clog << "Invalid sockaddr length: " << len << '\n';
            return {};
        }
        return to_string(a);
    }
    default:
    {
        std::clog << "Invalid sockaddr family: " << addr.ss_family << '\n';
        return {};
    }
    }
}

std::string get_iface_name(sockaddr_in const& addr)
{
    ifaddrs *ifa = nullptr;
    if (getifaddrs(&ifa) == -1)
    {
        std::clog << "getifaddrs: " << strerror(errno) << '\n';
        return {};
    }
    std::unique_ptr<ifaddrs, void(*)(ifaddrs*)>
        finally{ifa, freeifaddrs};

    for (; ifa; ifa = ifa->ifa_next)
    {
        if (!ifa->ifa_addr)
            continue;
        if (!ifa->ifa_name)
            continue;
        if (ifa->ifa_addr->sa_family != AF_INET)
            continue;
        auto& a = reinterpret_cast<sockaddr_in&>(*ifa->ifa_addr);
        if (a.sin_addr.s_addr == addr.sin_addr.s_addr)
            return ifa->ifa_name;
    }

    std::clog << "No interface found for IPv4 address " << to_string(addr) << '\n';
    return {};
}

std::string get_iface_name(sockaddr_in6 const& addr)
{
    ifaddrs *ifa = nullptr;
    if (getifaddrs(&ifa) == -1)
    {
        std::clog << "getifaddrs: " << strerror(errno) << '\n';
        return {};
    }
    std::unique_ptr<ifaddrs, void(*)(ifaddrs*)>
        finally{ifa, freeifaddrs};

    for (; ifa; ifa = ifa->ifa_next)
    {
        if (!ifa->ifa_addr)
            continue;
        if (!ifa->ifa_name)
            continue;
        if (ifa->ifa_addr->sa_family != AF_INET6)
            continue;
        auto& a = reinterpret_cast<sockaddr_in6&>(*ifa->ifa_addr);
        if (memcmp(a.sin6_addr.s6_addr,
                   addr.sin6_addr.s6_addr,
                   sizeof(a.sin6_addr.s6_addr)) == 0)
            return ifa->ifa_name;
    }

    std::clog << "No interface found for IPv6 address " << to_string(addr) << '\n';
    return {};
}

std::string get_iface_name(sockaddr_storage const& addr, socklen_t len)
{
    switch (addr.ss_family)
    {
    case AF_INET:
    {
        auto& a = reinterpret_cast<sockaddr_in const&>(addr);
        if (len < sizeof(a))
        {
            std::clog << "Invalid sockaddr length: " << len << '\n';
            return {};
        }
        return get_iface_name(a);
    }
    case AF_INET6:
    {
        auto& a = reinterpret_cast<sockaddr_in6 const&>(addr);
        if (len < sizeof(a))
        {
            std::clog << "Invalid sockaddr length: " << len << '\n';
            return {};
        }
        return get_iface_name(a);
    }
    default:
    {
        std::clog << "Invalid sockaddr family: " << addr.ss_family << '\n';
        return {};
    }
    }
}

std::string get_iface_name(int sockfd)
{
    sockaddr_storage addr;
    socklen_t len = sizeof(addr);
    if (getsockname(sockfd, (sockaddr*)&addr, &len) == -1)
    {
        std::clog << "getsockname: " << strerror(errno) << '\n';
        return {};
    }
    std::clog << "getsockname '" << to_string(addr, len) << '\'' << '\n';
    return get_iface_name(addr, len);
}

Ответ 4 · рейтинг 2 · Aiden Bell

Таблица маршрутизации ядра решает, на какой интерфейс отправить пакет, отсюда и возможность связывания устройств. Беглый просмотр "Linux Socket Programming, Warren W. Gay" говорит о том, что указание интерфейса - это плохо, и что из-за динамики ядра (брандмауэр, переадресация) он более сложен.

Я бы предложил изменить вашу IP-схему таким образом, чтобы IP-информация говорила вам о вашем интерфейсе (ах), просматривая его так же, как это делает ifconfig, в противном случае вы стреляете себе в ногу с точки зрения дизайна.

1) Получите информацию об IP-адресе из сеанса TCP. 2) Найдите, для какого интерфейса (ов) это может быть допустимо.

Однако я буду продолжать искать API ядра. Вам не нужно знать это, абстракция существует по множеству веских причин.

Дополнительная мысль Если подумать над этим, кажется, что если оба интерфейса используют один и тот же IP-адрес, то должна быть разница в маршрутизации диапазонов клиентских адресов (в противном случае использовались бы оба интерфейса). Ваш сервер может проверить таблицу маршрутизации на основе IP-адреса клиента.

Ответ 5 · рейтинг 1 · Wuggy

Я думаю, что использование getsockname() после accept() входящего соединения может быть тем, что вам нужно. Две функции getsockname() и getpeername() получают соответственно локальный и удаленный адреса, к которым привязан сокет. Оба должны быть действительны для полностью подключенного сокета TCP.

Редактировать: хотя это кажется верным для OpenBSD в соответствии с справочной страницей, справочная страница Linux значительно отличается, и поэтому getockname() после accept() в Linux почти наверняка бесполезен. Учит меня пользоваться памятью вместо того, чтобы все проверять. вздыхает

Ответ 6 · JimB

Посмотрите на адрес назначения.

Каждый интерфейс обычно привязан к уникальному адресу. Если несколько интерфейсов связаны друг с другом, вероятно, не имеет значения, какой из них используется.

Единственным исключением из этого является использование ipv6 anycast, но даже в этом случае у вас обычно не будет нескольких интерфейсов на одном хосте с одним и тем же IP-адресом.

Ответ 7 · Kieron

Очевидно, это не то, что я очень глубоко изучил, не говоря уже о том, чтобы попробовать, это может быть что-то для корзины "настолько безумно, что это может сработать" ...

Если это действительно когда-либо будет только для Linux, вы можете написать собственный модуль netfilter, который отслеживает входящие подключения. и отмечает, через какой интерфейс они приходят, и записывает эту информацию куда-нибудь для чтения вашим серверным приложением.

Ответ 8 · Leeor

Предложение Kieron написать модуль netfilter, вероятно, является одним из способов попробовать, но я хотел бы воздержаться от написания моего самого первого модуля ядра для этого решения.

Я придумал еще один вариант использования исходного NAT и перевода исходного порта соединения, чтобы он соответствовал источнику соединения. Я могу назначить диапазоны портов для каждой сети и проверить их на сервере. Единственная проблема заключается в том, что исходный NAT в iptables выполняется в цепочке POSTROUTING, и я не уверен, что он используется для соединений, принимаемых этим хостом, поэтому мне может понадобиться использовать другой сервер.

Здесь нет простых решений, очень жаль, что я не могу получить имя/индекс интерфейса из сокета...

Ответ 9 · Aiden Bell

Я добавляю еще один ответ и потенциальное решение после просмотра источника Wireshark и iftop, которые, похоже, имеют косвенно аналогичную функциональность.

Мне кажется, что вы можете использовать libpcap для обнюхивания интерфейсов. Предполагая, что вы можете идентифицировать какую-то уникальную часть сеанса TCP/IP, вы можете довольно просто отследить его до интерфейса, используя фильтры и отслеживание сеанса.

Никаких модулей ядра (и это хорошо работает с потоками)

http://www.ex-parrot.com/pdw/iftop/ Некоторые простой источник, чтобы заглянуть на www.tcpdump.org/ для libpcap

Я думаю, вы также сможете сопоставлять VLAN, используя его.

Также wireshark может быть полезен для отладки. Надеюсь это поможет! С тех пор это было у меня в голове.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы