如何获取与TCP套接字关联的接口名称/索引?

时间:2009-05-11 13:11:19

标签: linux sockets tcp

我正在编写一个TCP服务器,需要知道每个连接来自哪个接口。我无法使用地址/子网来推断使用了哪个接口,因为可能存在具有相同地址/子网值的接口。它基于Linux,并且不需要代码可移植。

我所能找到的只是通过索引获取所有接口或单个接口的函数。我找不到任何方法来获得与已接受的TCP套接字关联的接口。

有什么想法吗?我错过了什么?

编辑:重申一下,在我的情况下,IP地址并不是唯一的。目标地址(服务器本身)和源地址(客户端)都不是。 是的,这是一个非常极端的IP方案。

9 个答案:

答案 0 :(得分:4)

通常,您不需要知道要发送/接收数据包的接口;这是内核的路由表的工作。很难找到套接字的接口,因为它确实没有直接关联。数据包的路由可以根据路由信息在套接字的生命周期内发生变化。

对于数据报(UDP)套接字,您可以使用getsockopt(s, IPPROTO_IP, IP_PKTINFO, ...);请参阅getsockopt(2)ip(7)

对于流(TCP)套接字,一个选项可能是打开多个侦听套接字,一个用于系统上的每个接口,并使用setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ...)将每个接口绑定到一个接口;请参阅setsockopt(2)socket(7)

答案 1 :(得分:4)

使用getsockname()获取TCP连接本地端的IP。 然后使用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 :(得分:2)

内核路由表决定将数据包发送到哪个接口,从而能够绑定设备。粗略地浏览“Linux Socket Programming,Warren W. Gay”表明,指定一个接口很糟糕,而且由于内核的动态(防火墙,转发),它更复杂。

我建议改变你的IP方案,以便IP信息通过ifconfig以相同的方式查询你的界面,否则你是在脚设计方面拍摄自己。

1)从TCP会话中获取IP信息 2)查找哪个接口可能对

有效

我会继续查看内核API。你不应该知道这一点,抽象是有很多充分理由的。

额外的想法 考虑到这一点,似乎如果两个接口使用相同的IP,则必须存在客户端地址范围路由差异(否则将使用两个接口)。您的服务器可以根据客户端IP检查路由表

答案 3 :(得分:2)

这是一些用于查找套接字接口名称的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 :(得分:1)

我认为在接受传入连接之后使用getsockname()可能就是你所追求的。两个函数getsockname()和getpeername()分别获取套接字绑定的本地和远程地址。两者都应该对完全连接的TCP套接字有效。

编辑:虽然根据手册页对OpenBSD来说似乎也是如此,但是Linux手册页差异很大,因此在Linux上的accept()之后的getsockname()几乎肯定是无用的。教我使用我的记忆而不是检查一切。 叹息

答案 5 :(得分:0)

查看目的地地址。

每个接口通常绑定到一个唯一的地址。如果将多个接口绑定在一起,则使用哪个接口可能无关紧要。

唯一的例外是当使用ipv6任播时,但即使这样,你通常也不会在同一主机上有多个具有相同ip的接口。

答案 6 :(得分:0)

显然不是我深入研究过的东西,更不用说尝试了,这可能是一个“太疯狂它可能会起作用”的篮子......

如果它真的只适用于Linux,你可以编写一个自定义的netfilter模块来跟踪传入的连接并记录它们所接入的接口,并将该信息写入某处供服务器应用程序读取

答案 7 :(得分:0)

Kieron建议编写netfilter模块可能是一种尝试方法,但我想不要为此解决方案编写我的第一个内核模块。

我想出了另一种选择,即使用源NAT并转换连接的源端口以与连接源相关联。我可以为每个网络分配端口范围,并在服务器中进行检查。唯一的问题是iptables中的源NAT是在POSTROUTING链中完成的,我不确定它是否用于该主机接受的连接,所以我可能需要使用另一台服务器。

这里没有简单的解决方案,太糟糕了我无法从套接字中获取接口名称/索引...

答案 8 :(得分:0)

在查看Wireshark和iftop的源代码之后,我正在添加另一个答案和一个潜在的解决方案,它似乎具有间接相似的功能。

在我看来,您可以使用libpcap来嗅探接口。假设您可以识别TCP / IP会话的一些独特部分,那么您可以使用过滤器和会话跟踪相当简单地将其跟踪到界面。

没有内核模块(并且它对线程很好)

http://www.ex-parrot.com/pdw/iftop/一些简单的资料来看看 www.tcpdump.org/ for libpcap

我认为您也可以使用它匹配VLAN。

wireshark也可能对调试很有用。希望这可以帮助!从那时起它一直存在于我的大脑中。