我正在编写一个TCP服务器,需要知道每个连接来自哪个接口.我无法使用地址/子网来推断使用了哪个接口,因为可能存在具有相同地址/子网值的接口.它基于Linux,并且不需要代码可移植.
我所能找到的只是通过索引获取所有接口或单个接口的函数.我找不到任何方法来获得与接受的TCP套接字关联的接口.
有任何想法吗?我错过了什么?
编辑:重申一下,在我的情况下,IP地址并不是唯一的.目标地址(服务器本身)和源地址(客户端)都不是.是的,这是一个非常极端的IP方案.
使用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);
Run Code Online (Sandbox Code Playgroud)
以上只是一个肮脏的例子,需要一些修改:
小智 5
以下是一些用于查找套接字接口名称的 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);
}
Run Code Online (Sandbox Code Playgroud)
查看目标地址。
每个接口通常绑定到一个唯一的地址。如果多个接口绑定在一起,那么使用哪一个可能并不重要。
唯一的例外是使用 ipv6 任播时,但即使如此,通常也不会在同一台主机上拥有多个具有相同 IP 的接口。