简体   繁体   English

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

[英]How can I get the interface name/index associated with a TCP socket?

I'm writing a TCP server that needs to know which interface each connection arrived from.我正在编写一个 TCP 服务器,它需要知道每个连接来自哪个接口。 I cannot use the address/subnet to deduce which interface was used, since there might be interfaces with the same address/subnet values.我无法使用地址/子网来推断使用了哪个接口,因为可能存在具有相同地址/子网值的接口。 It's Linux based, and there's no need for the code to be portable.它基于 Linux,不需要代码可移植。

All I could find were functions to get all interfaces, or a single interface by index.我能找到的只是获取所有接口的函数,或者按索引获取单个接口。 I could not find any way to get the interface associated with an accepted TCP socket.我找不到任何方法来获取与接受的 TCP 套接字关联的接口。

Any ideas?有任何想法吗? Something I've missed?我错过了什么?

EDIT: To reiterate, IP addresses are not unique in my case.编辑:重申一下,IP 地址在我的情况下不是唯一的。 Neither the destination addresses (the server itself) nor the source addresses (the clients).既不是目标地址(服务器本身)也不是源地址(客户端)。 Yes, this is a very extreme IP scheme.是的,这是一个非常极端的IP方案。

Use getsockname() to get IP of local end of the TCP connection.使用getsockname()获取TCP 连接本地端的IP。 Then use getifaddrs() to find the corresponding interface:然后使用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);

Above is just a dirty example, some modifications are needed:以上只是一个肮脏的例子,需要一些修改:

  1. Add missing error checks添加缺少的错误检查
  2. IPv6 support IPv6 支持

Here's some C++11 code to find the interface name of a socket:这是一些用于查找套接字接口名称的 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);
}

In general, you shouldn't need to know what interface the packets are going to be sent/received on;通常,您不需要知道将在哪个接口上发送/接收数据包; that's the kernel's routing table's job.那是内核路由表的工作。 It's difficult to find out the interface for a socket because there really is no direct association.很难找到套接字的接口,因为确实没有直接关联。 The routing of packets can change within the socket's lifetime based on routing information.数据包的路由可以根据路由信息在套接字的生命周期内改变。

For datagram (UDP) sockets, you may be able to use getsockopt(s, IPPROTO_IP, IP_PKTINFO, ...) ;对于数据报 (UDP) 套接字,您可以使用getsockopt(s, IPPROTO_IP, IP_PKTINFO, ...) see getsockopt(2) and ip(7) .请参阅getsockopt(2)ip(7)

For stream (TCP) sockets, one option might be to open multiple listening sockets, one for each interface on the system, and use setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ...) to bind each to one interface;对于流 (TCP) 套接字,一种选择可能是打开多个侦听套接字,系统上的每个接口一个,并使用setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ...)将每个套接字绑定到一个接口; see setsockopt(2) and socket(7) .请参阅setsockopt(2)socket(7)

The kernel routing table decides which interface to send a packet out on, hence the ability to bond devices.内核路由表决定将数据包发送到哪个接口,从而能够绑定设备。 A cursory glance through "Linux Socket Programming, Warren W. Gay" suggests that specifying an interface is bad, and that due to the dynamics of the kernel (firewall, forwarding) it is more complex.粗略浏览“Linux Socket Programming, Warren W. Gay”表明指定接口是不好的,而且由于内核的动态性(防火墙、转发),它更加复杂。

I would suggest altering your IP scheme such that the IP information tells you your interface(s) through looking up in the same way ifconfig does, otherwise you are shooting yourself in the foot design wise.我建议更改您的 IP 方案,以便 IP 信息通过与 ifconfig 相同的方式查找来告诉您您的接口,否则您将在脚设计明智中射击自己。

1) Get the IP information from the TCP session 2) Lookup which interface(s) this could be valid for 1) 从 TCP 会话中获取 IP 信息 2) 查找这可能对哪些接口有效

I will keep looking in the kernel API though.不过,我会继续查看内核 API。 You shouldn't need to know this, the abstraction is there for a multitude of good reasons.您不需要知道这一点,抽象的存在有很多充分的理由。

Extra Thought Pondering on this, it seems that if both interfaces use the same IP then there must be a client address range routing difference (otherwise both interfaces would be used).对此的额外思考,似乎如果两个接口都使用相同的 IP,那么肯定存在客户端地址范围路由差异(否则将使用两个接口)。 Your server could examine the routing table based on the client IP您的服务器可以根据客户端 IP 检查路由表

I think using getsockname() after accept()ing the incoming connection might be what you're after.我认为在 accept() 传入连接之后使用 getsockname() 可能是您所追求的。 The two functions getsockname() and getpeername() get the local and remote addresses respectively that a socket is bound to.两个函数getsockname() 和getpeername() 分别获取套接字绑定到的本地和远程地址。 Both should be valid for a fully connected TCP socket.两者都应该对完全连接的 TCP 套接字有效。

Edit: Whilst this seems to be true for OpenBSD according to the man page, the Linux man page differs considerably and so getsockname() after accept() on Linux is almost certainly unuseful.编辑:虽然根据手册页对于 OpenBSD 来说这似乎是正确的,但 Linux 手册页有很大不同,因此在 Linux 上 accept() 之后的 getockname() 几乎肯定是没有用的。 Teaches me for using my memory instead of checking everything.教我使用我的记忆而不是检查一切。 sigh

Look at the destination address.查看目标地址。

Each interface is normally bound to a unique address.每个接口通常绑定到一个唯一的地址。 If multiple interfaces are bonded together, it probably doesn't matter which one it used.如果多个接口绑定在一起,使用哪一个可能并不重要。

The only exception to this, is when using ipv6 anycast, but even then, you wouldn't normally have multiple interfaces on the same host with the same ip.唯一的例外是使用 ipv6 任播时,但即便如此,您通常也不会在同一主机上有多个接口具有相同的 ip。

Obviously not something I've looked into very deeply, let alone tried, this might be one for the "so crazy it just might work" basket...显然不是我深入研究过的东西,更不用说尝试了,这可能是“如此疯狂它可能会起作用”的篮子......

If it's really only ever going to be for Linux, you could write a custom netfilter module which tracks the incoming connections and notes which interface they come in on, and writes that information somewhere for your server application to read.如果它真的只适用于 Linux,您可以编写一个自定义netfilter模块来跟踪传入的连接并记录它们进入的接口,并将该信息写入某处供您的服务器应用程序读取。

Kieron's suggestion to write a netfilter module is probably one way to try, but I would like to refrain from writing my very first kernel module for this solution. Kieron 建议编写一个 netfilter 模块可能是一种尝试方法,但我想避免为此解决方案编写我的第一个内核模块。

I've come up with another option of using source NAT and translating the source port of the connection to correlate with the connection source.我想出了另一个选项,即使用源 NAT 并将连接的源端口转换为与连接源相关联。 I can assign port ranges for each network and check it in the server.我可以为每个网络分配端口范围并在服务器中检查它。 The only problem is that source NAT in iptables is done in the POSTROUTING chain, and I'm not sure it's used for connections that are accepted by that host, so I might need to use another server.唯一的问题是 iptables 中的源 NAT 是在 POSTROUTING 链中完成的,我不确定它是否用于该主机接受的连接,因此我可能需要使用另一台服务器。

No easy solutions here, too bad I can't get the interface name/index from the socket...这里没有简单的解决方案,太糟糕了,我无法从套接字中获取接口名称/索引...

I am adding another answer and a potential solution after looking through the source of Wireshark and iftop which seem to have indirectly similar functionality.在查看了 Wireshark 和 iftop 的来源后,我正在添加另一个答案和一个潜在的解决方案,它们似乎具有间接相似的功能。

Looks to me that you can use libpcap to sniff on interfaces.在我看来,您可以使用 libpcap 来嗅探接口。 Presuming you can identify some unique part of the TCP/IP session then you can track it down to an interface fairly simply using filters and session tracking.假设您可以识别 TCP/IP 会话的某些独特部分,然后您可以使用过滤器和会话跟踪相当简单地将其跟踪到接口。

No kernel modules (and it plays nice with threads)没有内核模块(它与线程一起玩得很好)

http://www.ex-parrot.com/pdw/iftop/ Some simple source to have a peek at www.tcpdump.org/ for libpcap http://www.ex-parrot.com/pdw/iftop/在 www.tcpdump.org/ 上查看 libpcap 的一些简单来源

I think you will be able to match VLANs using it too.我认为您也可以使用它来匹配 VLAN。

Also wireshark might be useful for debugging.此外,wireshark 可能对调试有用。 Hope this helps!希望这可以帮助! It's been on my brain since.从那以后它就一直在我的脑海里。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM