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

15,135

Solution 1

Use getsockname() to get IP of local end of the TCP connection. Then use getifaddrs() to find the corresponding interface:

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

Solution 2

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);
}

Solution 3

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

Solution 4

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

Solution 5

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

Share:
15,135
Leeor
Author by

Leeor

Updated on June 23, 2022

Comments

  • Leeor
    Leeor almost 2 years

    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 might be interfaces with the same address/subnet values. It's Linux based, and there's no need for the code to be portable.

    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.

    Any ideas? Something I've missed?

    EDIT: To reiterate, IP addresses are not unique in my case. Neither the destination addresses (the server itself) nor the source addresses (the clients). Yes, this is a very extreme IP scheme.

  • Leeor
    Leeor about 15 years
    In this case, the interfaces are not bound to unique addresses. They are not bonded either. They are connected to different networks, although they have overlapping/identical IPs.
  • JimB
    JimB about 15 years
    If you have a tcp connection, you must have an ip address. You don't have to bind a socket to a particular address, but your interface needs one
  • Leeor
    Leeor about 15 years
    Yes, the connection has an IP address, but it's not unique. It's not unique for that interface, and it's not unique for that network. I really need a way to get the interface from the socket itself.
  • JimB
    JimB about 15 years
    It seems you have an unusual case. I'll leave my answer as is, as it's logical for most people's cases, and @Aiden answered with what I was going to add.
  • Leeor
    Leeor about 15 years
    The client IPs can overlap as well. The difference that allows these IPs to overlap is the VLAN id. As far as I know, that's inaccessible as well.
  • Aiden Bell
    Aiden Bell about 15 years
    you may have found the one situation where you can't use circumstantial evidence to determine interface. I don't think there will be a way, as the interface is kind of abstract from applications higher than the kernel. You could maybe modify your kernel to provide some data through /proc/tcp_sessions_to_devs or something but that is VERY - erm - desperate?
  • Leeor
    Leeor about 15 years
    Yes, setting separate socket for each interface would work, but seeing as there are dozens of interfaces and they are brought up and taken down dynamically, it's something I want to avoid.
  • Bell
    Bell about 15 years
    Another netfilter option is to forward the traffic to additional local interfaces that have been set up with distinct IP adresses.
  • Aiden Bell
    Aiden Bell about 15 years
    Another thought. Trying looking into the interface sniffing libraries that Wireshark uses (libpcap) and look at how the source of iftop enumerates sessions on a device. Might be interesting.
  • Leeor
    Leeor about 15 years
    Yep, that sounds like another option. Thanks for the effort!
  • thang
    thang over 9 years
    actually, i tried it and this solution works in linux and windows
  • EML
    EML about 7 years
    Wrong 'name' - this returns the address. The OP wants the interface name, ie. eth0/etc. As the glibc docs say, "the functions and symbols for dealing with socket addresses were named inconsistently, sometimes using the term 'name', and sometimes using 'address'".
  • Alexis Wilke
    Alexis Wilke almost 6 years
    Note that this if (inaddr->sin_addr.s_addr == addr.sin_addr.s_addr) probably never matches. I would suggest you apply the netmask to improve chances a tad bit: if (inaddr->sin_addr.s_addr == (addr.sin_addr.s_addr & ((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr))
  • SKi
    SKi almost 6 years
    @AlexisWilke : We have used this kind of if-statement in production software for years without problems. I don't understood why it would not match to any item in the list, because inaddr contains local (not peer or sub-net) IP address.
  • Alexis Wilke
    Alexis Wilke almost 6 years
    Right, in my case I want to find the interface that corresponds to any address, so I could have 192.168.3.111 which won't match 192.168.3.0 unless I first apply the mask of the interface.
  • Mecki
    Mecki almost 4 years
    You can only determine the interface by looking at the routing table, not at the interface configuration. Despite the fact at that multiple interfaces may be attached to the same IP network, multiple interface can even have the same IP address. And regardless which interface traffic would usually take, I can always add a route and force it over a different interface. It's the routing table that decides which interface a packet will leave the system and nobody else. Interface settings just create entries in the routing table but only the table knows the truth.
  • Mecki
    Mecki almost 4 years
    Multiple interfaces can belong to the same IP network, multiple interface can even have the same IP address. Which interface is being used to send traffic can only be determined by looking at the routing table.
  • Mecki
    Mecki almost 4 years
    Fails if I set a route for the detonation address to point to a different interface. The process for deciding which interface traffic will take is named routing and the routing table is the relevant data source.