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

前端 未结 9 479
伪装坚强ぢ
伪装坚强ぢ 2021-01-12 04:17

I\'m writing a TCP server that needs to know which interface each connection arrived from. I cannot use the address/subnet to deduce which interface was used, since there mi

相关标签:
9条回答
  • 2021-01-12 05:04

    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.

    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. 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.

    No easy solutions here, too bad I can't get the interface name/index from the socket...

    0 讨论(0)
  • 2021-01-12 05:05

    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.

    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.

    1) Get the IP information from the TCP session 2) Lookup which interface(s) this could be valid for

    I will keep looking in the kernel API though. 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). Your server could examine the routing table based on the client IP

    0 讨论(0)
  • 2021-01-12 05:08

    Here's some C++11 code to find the interface name of a socket:

    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);
    }
    
    0 讨论(0)
  • 2021-01-12 05:09

    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, ...); see getsockopt(2) and 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; see setsockopt(2) and socket(7).

    0 讨论(0)
  • 2021-01-12 05:13

    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.

    Looks to me that you can use libpcap to sniff on interfaces. 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.

    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

    I think you will be able to match VLANs using it too.

    Also wireshark might be useful for debugging. Hope this helps! It's been on my brain since.

    0 讨论(0)
  • 2021-01-12 05:19

    I think using getsockname() after accept()ing the incoming connection might be what you're after. The two functions getsockname() and getpeername() get the local and remote addresses respectively that a socket is bound to. Both should be valid for a fully connected TCP socket.

    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. Teaches me for using my memory instead of checking everything. sigh

    0 讨论(0)
提交回复
热议问题