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

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

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

Есть идеи? Что-то я пропустил?

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

Ответы [ 9 ]

4 голосов
/ 23 июня 2016

Используйте 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
4 голосов
/ 12 мая 2009

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

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

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

2 голосов
/ 18 апреля 2018

Вот код 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);
}
2 голосов
/ 11 мая 2009

Таблица маршрутизации ядра определяет, через какой интерфейс отправлять пакеты, и, следовательно, возможность связывать устройства. Беглый взгляд на «Программирование на сокете Linux, Уоррен У. Гей» показывает, что указание интерфейса - это плохо, и что из-за динамики ядра (брандмауэр, переадресация) он более сложен.

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

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

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

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

1 голос
/ 11 мая 2009

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

Редактировать: Хотя, похоже, что это верно для OpenBSD в соответствии с man-страницей, man-страница Linux значительно отличается, и поэтому getsockname () после accept () в Linux почти наверняка не используется. Обучает меня тому, как использовать память вместо проверки всего. Вздох

0 голосов
/ 14 мая 2009

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

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

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

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

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

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

0 голосов
/ 12 мая 2009

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

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

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

0 голосов
/ 12 мая 2009

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

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

0 голосов
/ 11 мая 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...