mirror of
https://github.com/espressif/openthread.git
synced 2026-11-02 12:33:22 +00:00
[posix] fix mDNS local loopback discovery and socket options (#13321)
Address local discovery issues where co-located applications running on the same Linux host with host networking cannot discover each other via mDNS. 1. Remove SO_BINDTODEVICE on Linux in OpenIp4Socket and OpenIp6Socket in src/posix/platform/mdns_socket.cpp. While SO_BINDTODEVICE filters out traffic from unintended network interfaces, it also causes the Linux kernel (compute_score in net/ipv4/udp.c) to silently reject and drop local loopback mDNS queries and announcements (PACKET_LOOPBACK) sent between co-located applications on the same host. On non-Linux platforms (#else for macOS and BSD), continue to use IP_BOUND_IF / IPV6_BOUND_IF, which restricts sockets to the infrastructure interface without dropping loopback datagrams. 2. Conditionally exclude SO_REUSEPORT on Linux (#ifndef __linux__) in SetReuseAddrPortOptions in both mdns_socket.cpp and dhcp6_pd_socket.cpp. Per RFC 6762 Section 15.1, coexisting mDNS responders on a single host must use SO_REUSEADDR so that the operating system duplicates every incoming UDP multicast datagram to every bound socket. On Linux, SO_REUSEPORT groups sockets of the same UID into a single eBPF/4-tuple hash bucket (reuseport_select_sock) and delivers each packet to only one socket, stealing datagrams from other local mDNS and DHCPv6 listeners. Keeping SO_REUSEPORT on non-Linux systems preserves required macOS and BSD port-sharing compatibility. 3. Enable IP_PKTINFO / IPV6_RECVPKTINFO and update ReceiveMessage to use recvmsg() on Linux in mdns_socket.cpp. Because SO_BINDTODEVICE was removed on Linux, sockets now receive datagrams across all network interfaces on the host. To ensure datagrams arriving from non-infrastructure interfaces (e.g. wpan0 or virtual bridges) are not mistakenly processed as if they arrived on the infrastructure interface, ReceiveMessage parses ancillary control data on Linux to inspect the received interface index (ipi_ifindex / ipi6_ifindex). Packets arriving from interfaces matching neither mInfraIfIndex nor the loopback interface (checked via strcmp against "lo" or ioctl SIOCGIFFLAGS for IFF_LOOPBACK) are safely discarded. Ensure cmsgBuffer is properly aligned using alignas(struct cmsghdr).
This commit is contained in:
@@ -419,7 +419,9 @@ otError Dhcp6PdSocket::SetReuseAddrPortOptions(int aFd)
|
||||
otError error;
|
||||
|
||||
SuccessOrExit(error = SetSocketOption<int>(aFd, SOL_SOCKET, SO_REUSEADDR, 1, "SO_REUSEADDR"));
|
||||
#ifndef __linux__
|
||||
SuccessOrExit(error = SetSocketOption<int>(aFd, SOL_SOCKET, SO_REUSEPORT, 1, "SO_REUSEPORT"));
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return error;
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <unistd.h>
|
||||
#ifdef __linux__
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#include <openthread/platform/time.h>
|
||||
@@ -419,6 +420,12 @@ void MdnsSocket::SendQueuedMessages(MsgType aMsgType)
|
||||
addr6.sin6_family = AF_INET6;
|
||||
addr6.sin6_port = htons(metadata.mIp6Port);
|
||||
CopyIp6AddressTo(metadata.mIp6Address, &addr6.sin6_addr);
|
||||
|
||||
if (IsIp6AddressLinkLocal(metadata.mIp6Address))
|
||||
{
|
||||
addr6.sin6_scope_id = mInfraIfIndex;
|
||||
}
|
||||
|
||||
bytesSent = sendto(mFd6, buffer, length, 0, reinterpret_cast<struct sockaddr *>(&addr6), sizeof(addr6));
|
||||
VerifyOrExit(bytesSent == length);
|
||||
metadata.mIp6Port = 0;
|
||||
@@ -452,46 +459,153 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static bool IsLoopbackInterface(int aIfIndex)
|
||||
{
|
||||
static int sLastIfIndex = -1;
|
||||
static bool sLastIsLoopback = false;
|
||||
bool isLoopback = false;
|
||||
char ifName[IF_NAMESIZE];
|
||||
|
||||
VerifyOrExit(aIfIndex > 0);
|
||||
|
||||
if (aIfIndex == sLastIfIndex)
|
||||
{
|
||||
isLoopback = sLastIsLoopback;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
VerifyOrExit(if_indextoname(static_cast<unsigned int>(aIfIndex), ifName) != nullptr);
|
||||
|
||||
if (strcmp(ifName, "lo") == 0)
|
||||
{
|
||||
isLoopback = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
|
||||
if (fd >= 0)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, ifName, sizeof(ifr.ifr_name) - 1);
|
||||
|
||||
if (ioctl(fd, SIOCGIFFLAGS, &ifr) == 0)
|
||||
{
|
||||
isLoopback = ((ifr.ifr_flags & IFF_LOOPBACK) != 0);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
sLastIfIndex = aIfIndex;
|
||||
sLastIsLoopback = isLoopback;
|
||||
|
||||
exit:
|
||||
return isLoopback;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MdnsSocket::ReceiveMessage(MsgType aMsgType)
|
||||
{
|
||||
otMessage *message = nullptr;
|
||||
uint8_t buffer[kMaxMessageLength];
|
||||
otPlatMdnsAddressInfo addrInfo;
|
||||
uint16_t length = 0;
|
||||
struct sockaddr_in6 sockaddr6;
|
||||
struct sockaddr_in sockaddr;
|
||||
socklen_t len = sizeof(sockaddr6);
|
||||
ssize_t rval;
|
||||
otMessage *message = nullptr;
|
||||
uint8_t buffer[kMaxMessageLength];
|
||||
otPlatMdnsAddressInfo addrInfo;
|
||||
uint16_t length = 0;
|
||||
struct sockaddr_storage peerAddr;
|
||||
struct iovec iov;
|
||||
struct msghdr msg;
|
||||
alignas(struct cmsghdr) char cmsgBuffer[1024];
|
||||
ssize_t rval;
|
||||
#ifdef __linux__
|
||||
int ifIndex = -1;
|
||||
#endif
|
||||
|
||||
memset(&addrInfo, 0, sizeof(addrInfo));
|
||||
memset(&peerAddr, 0, sizeof(peerAddr));
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(cmsgBuffer, 0, sizeof(cmsgBuffer));
|
||||
|
||||
iov.iov_base = buffer;
|
||||
iov.iov_len = sizeof(buffer);
|
||||
msg.msg_name = &peerAddr;
|
||||
msg.msg_namelen = sizeof(peerAddr);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsgBuffer;
|
||||
msg.msg_controllen = sizeof(cmsgBuffer);
|
||||
|
||||
switch (aMsgType)
|
||||
{
|
||||
case kIp6Msg:
|
||||
len = sizeof(sockaddr6);
|
||||
memset(&sockaddr6, 0, sizeof(sockaddr6));
|
||||
rval = recvfrom(mFd6, reinterpret_cast<char *>(&buffer), sizeof(buffer), 0,
|
||||
reinterpret_cast<struct sockaddr *>(&sockaddr6), &len);
|
||||
VerifyOrExit(rval >= 0, LogCrit("recvfrom() for IPv6 socket failed, errno: %s", strerror(errno)));
|
||||
rval = recvmsg(mFd6, &msg, 0);
|
||||
VerifyOrExit(rval >= 0, LogCrit("recvmsg() for IPv6 socket failed, errno: %s", strerror(errno)));
|
||||
length = static_cast<uint16_t>(rval);
|
||||
ReadIp6AddressFrom(&sockaddr6.sin6_addr, addrInfo.mAddress);
|
||||
addrInfo.mPort = ntohs(sockaddr6.sin6_port);
|
||||
break;
|
||||
|
||||
case kIp4Msg:
|
||||
len = sizeof(sockaddr);
|
||||
memset(&sockaddr, 0, sizeof(sockaddr));
|
||||
rval = recvfrom(mFd4, reinterpret_cast<char *>(&buffer), sizeof(buffer), 0,
|
||||
reinterpret_cast<struct sockaddr *>(&sockaddr), &len);
|
||||
VerifyOrExit(rval >= 0, LogCrit("recvfrom() for IPv4 socket failed, errno: %s", strerror(errno)));
|
||||
rval = recvmsg(mFd4, &msg, 0);
|
||||
VerifyOrExit(rval >= 0, LogCrit("recvmsg() for IPv4 socket failed, errno: %s", strerror(errno)));
|
||||
length = static_cast<uint16_t>(rval);
|
||||
otIp4ToIp4MappedIp6Address((otIp4Address *)(&sockaddr.sin_addr.s_addr), &addrInfo.mAddress);
|
||||
addrInfo.mPort = ntohs(sockaddr.sin_port);
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyOrExit(length > 0);
|
||||
|
||||
#ifdef __linux__
|
||||
ifIndex = -1;
|
||||
|
||||
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg))
|
||||
{
|
||||
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
|
||||
{
|
||||
struct in_pktinfo pktinfo;
|
||||
|
||||
if (cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in_pktinfo)))
|
||||
{
|
||||
memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
|
||||
ifIndex = pktinfo.ipi_ifindex;
|
||||
}
|
||||
}
|
||||
else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO)
|
||||
{
|
||||
struct in6_pktinfo pktinfo;
|
||||
|
||||
if (cmsg->cmsg_len >= CMSG_LEN(sizeof(struct in6_pktinfo)))
|
||||
{
|
||||
memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
|
||||
ifIndex = static_cast<int>(pktinfo.ipi6_ifindex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrExit(ifIndex == static_cast<int>(mInfraIfIndex) || IsLoopbackInterface(ifIndex));
|
||||
#endif
|
||||
|
||||
switch (aMsgType)
|
||||
{
|
||||
case kIp6Msg:
|
||||
{
|
||||
struct sockaddr_in6 *sockaddr6 = reinterpret_cast<struct sockaddr_in6 *>(&peerAddr);
|
||||
|
||||
ReadIp6AddressFrom(&sockaddr6->sin6_addr, addrInfo.mAddress);
|
||||
addrInfo.mPort = ntohs(sockaddr6->sin6_port);
|
||||
break;
|
||||
}
|
||||
|
||||
case kIp4Msg:
|
||||
{
|
||||
struct sockaddr_in *sockaddr = reinterpret_cast<struct sockaddr_in *>(&peerAddr);
|
||||
|
||||
otIp4ToIp4MappedIp6Address(reinterpret_cast<otIp4Address *>(&sockaddr->sin_addr.s_addr), &addrInfo.mAddress);
|
||||
addrInfo.mPort = ntohs(sockaddr->sin_port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
message = otIp6NewMessage(mInstance, nullptr);
|
||||
VerifyOrExit(message != nullptr);
|
||||
SuccessOrExit(otMessageAppend(message, buffer, length));
|
||||
@@ -749,16 +863,7 @@ otError MdnsSocket::OpenIp4Socket(uint32_t aInfraIfIndex)
|
||||
VerifyOrExit(fd >= 0, LogCrit("Failed to create IPv4 socket"));
|
||||
|
||||
#ifdef __linux__
|
||||
{
|
||||
char nameBuffer[IF_NAMESIZE];
|
||||
const char *ifname;
|
||||
|
||||
ifname = if_indextoname(aInfraIfIndex, nameBuffer);
|
||||
VerifyOrExit(ifname != NULL, LogCrit("if_indextoname() failed"));
|
||||
|
||||
error = SetSocketOptionValue(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname), "SO_BINDTODEVICE");
|
||||
SuccessOrExit(error);
|
||||
}
|
||||
SuccessOrExit(error = SetSocketOption<int>(fd, IPPROTO_IP, IP_PKTINFO, 1, "IP_PKTINFO"));
|
||||
#else
|
||||
{
|
||||
int ifindex = static_cast<int>(aInfraIfIndex);
|
||||
@@ -842,20 +947,9 @@ otError MdnsSocket::OpenIp6Socket(uint32_t aInfraIfIndex)
|
||||
VerifyOrExit(fd >= 0, LogCrit("Failed to create IPv4 socket"));
|
||||
|
||||
#ifdef __linux__
|
||||
{
|
||||
char nameBuffer[IF_NAMESIZE];
|
||||
const char *ifname;
|
||||
|
||||
ifname = if_indextoname(aInfraIfIndex, nameBuffer);
|
||||
VerifyOrExit(ifname != NULL, LogCrit("if_indextoname() failed"));
|
||||
|
||||
error = SetSocketOptionValue(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname), "SO_BINDTODEVICE");
|
||||
SuccessOrExit(error);
|
||||
}
|
||||
SuccessOrExit(error = SetSocketOption<int>(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1, "IPV6_RECVPKTINFO"));
|
||||
#else
|
||||
{
|
||||
SuccessOrExit(error = SetSocketOption<int>(fd, IPPROTO_IPV6, IPV6_BOUND_IF, ifindex, "IPV6_BOUND_IF"));
|
||||
}
|
||||
SuccessOrExit(error = SetSocketOption<int>(fd, IPPROTO_IPV6, IPV6_BOUND_IF, ifindex, "IPV6_BOUND_IF"));
|
||||
#endif
|
||||
|
||||
SuccessOrExit(error = SetSocketOption<int>(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255, "IPV6_MULTICAST_HOPS"));
|
||||
@@ -930,7 +1024,9 @@ otError MdnsSocket::SetReuseAddrPortOptions(int aFd)
|
||||
otError error;
|
||||
|
||||
SuccessOrExit(error = SetSocketOption<int>(aFd, SOL_SOCKET, SO_REUSEADDR, 1, "SO_REUSEADDR"));
|
||||
#ifndef __linux__
|
||||
SuccessOrExit(error = SetSocketOption<int>(aFd, SOL_SOCKET, SO_REUSEPORT, 1, "SO_REUSEPORT"));
|
||||
#endif
|
||||
|
||||
exit:
|
||||
return error;
|
||||
|
||||
Reference in New Issue
Block a user