我找到的所有函数都是用于获取所有接口或通过索引获取单个接口,但我找不到任何方法来获取与已接受的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);
以上只是一个简单的示例,需要进行一些修改:
if (inaddr->sin_addr.s_addr == addr.sin_addr.s_addr)
可能永远不会匹配。我建议您应用子网掩码来稍微提高一点机会:if (inaddr->sin_addr.s_addr == (addr.sin_addr.s_addr & ((struct sockaddr_in*)ifa->ifa_netmask)->sin_addr.s_addr))
- Alexis Wilke以下是一段用于查找套接字接口名称的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);
}
getsockopt(s, IPPROTO_IP, IP_PKTINFO, ...)
;请参见getsockopt(2)
和ip(7)
。setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ...)
将每个套接字绑定到一个接口上;请参见setsockopt(2)
和socket(7)
。我认为在接受传入连接后使用getsockname()可能是你想要的。这两个函数getsockname()和getpeername()分别获取套接字绑定的本地和远程地址。对于完全连接的TCP套接字,两者都应该有效。
编辑:虽然根据OpenBSD的man页面似乎是正确的,但Linux的man页面差异很大,因此在Linux上接受连接后使用getsockname()几乎肯定没有用。教训是我应该检查一切而不是依靠记忆。 叹气
显然,这不是我深入研究过或尝试过的东西,但这可能是一个“如此疯狂以至于可能奏效”的选择...
如果它只会在Linux上使用,您可以编写一个自定义netfilter模块来跟踪传入连接并记录它们进入的接口,并将该信息写入某个位置供服务器应用程序读取。
Kieron 提出编写 netfilter 模块可能是一种尝试的方式,但我不想为此解决方案编写我的第一个内核模块。
我想到了另一种选项,即使用源地址转换 (source NAT) 并将连接的源端口进行转换以与连接源相关联。我可以为每个网络分配端口范围并在服务器中检查它。唯一的问题是 iptables 中的源地址转换是在 POSTROUTING 链中完成的,而且我不确定它是否用于被该主机接受的连接,因此我可能需要使用另一台服务器。
这里没有简单的解决方案,太糟糕了,我无法从套接字获取接口名称/索引...
看目标地址。
每个接口通常绑定到唯一的地址。如果多个接口被绑定在一起,使用哪一个可能并不重要。
唯一的例外是使用ipv6 anycast时,但即使如此,在同一主机上通常也不会有多个具有相同ip的接口。
我在查看Wireshark和iftop的源代码后,添加了另一个答案和潜在解决方案,它们似乎间接具有类似的功能。
在我看来,您可以使用libpcap来嗅探接口。假设您可以识别TCP/IP会话的某些唯一部分,那么您可以使用过滤器和会话跟踪将其追踪到接口。
没有内核模块(并且它与线程兼容)
http://www.ex-parrot.com/pdw/iftop/ 这里有一些简单的源代码供您参考 www.tcpdump.org/ 用于libpcap
我认为您也可以使用它来匹配VLAN。
此外,wireshark可能对调试有用。希望这可以帮助您!自从以来,这一直在我的脑海中。