Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do a mDNS advertisement on minmdns server startup #4884

Merged
merged 22 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e08ed9b
Listen on all IPv4 interfaces (not only NULL interface ID) so that re…
andy31415 Feb 16, 2021
67cd75b
Do a startup mDNS broadcast for available services - no filtering by …
andy31415 Feb 16, 2021
8d72191
Ensure mdns advertise at boot only happens on interfaces that it is a…
andy31415 Feb 16, 2021
df6955d
Remove FIXME since it was already addressed
andy31415 Feb 16, 2021
86acb92
Revise doc comment on boot time advertisement
andy31415 Feb 16, 2021
e28ffc6
Add a constant for maximum interface name length
andy31415 Feb 17, 2021
c047193
Restyle fixes
andy31415 Feb 17, 2021
d4b0efc
Fix LWIP interface name max size
andy31415 Feb 17, 2021
08d420e
Increase LWIP max size some more, to support for 32bit interface numb…
andy31415 Feb 17, 2021
756d32a
Update include file for zephyr interface name constant
andy31415 Feb 17, 2021
07e3e79
Merge branch 'master' into 01_mdns_advertise_at_boot
andy31415 Feb 17, 2021
9fecbf9
Deduplicate the detection of usable mdns interfaces
andy31415 Feb 18, 2021
e402b1d
Deduplicate ip addresses for global mdns broadcast
andy31415 Feb 18, 2021
2157ffc
Fix compilation
andy31415 Feb 18, 2021
c6127f6
Fix linkage too
andy31415 Feb 18, 2021
8c91e4d
do not use pre-main initialization for mDNS broadcast IP addresses
andy31415 Feb 19, 2021
708772e
Fix typo: Trottle -> Throttle
andy31415 Feb 19, 2021
076431b
Fix typo: Trottle -> Throttle
andy31415 Feb 19, 2021
d285703
Add todo to have better local loopback detection for interfaces
andy31415 Feb 19, 2021
68527cf
Log on IP parse error - we seem to crash in esp32 and am not sure why…
andy31415 Feb 19, 2021
fba34ab
Fix condition inversion
andy31415 Feb 19, 2021
88d1444
Fix logic error: IP address returns a bool not CHIP_ERROR
andy31415 Feb 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/minimal-mdns/AllInterfaceListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class AllInterfaces : public mdns::Minimal::ListenIterator
{
return true; // not a usable interface
}
char name[64];

char name[chip::Inet::InterfaceIterator::kMaxIfNameLength];
if (mIterator.GetInterfaceName(name, sizeof(name)) != CHIP_NO_ERROR)
{
printf("!!!! FAILED TO GET INTERFACE NAME\n");
Expand Down
15 changes: 15 additions & 0 deletions src/inet/InetInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct ifaddrs;
#endif // CHIP_SYSTEM_CONFIG_USE_BSD_IFADDRS

#if CHIP_SYSTEM_CONFIG_USE_ZEPHYR_NET_IF
#include <device.h>

struct net_if;
struct net_if_ipv4;
struct net_if_ipv6;
Expand Down Expand Up @@ -152,6 +154,19 @@ class InterfaceIterator
bool SupportsMulticast();
bool HasBroadcastAddress();

#if CHIP_SYSTEM_CONFIG_USE_LWIP
static constexpr size_t kMaxIfNameLength = 13; // Names are formatted as %c%c%d
#elif CHIP_SYSTEM_CONFIG_USE_SOCKETS && CHIP_SYSTEM_CONFIG_USE_BSD_IFADDRS
static constexpr size_t kMaxIfNameLength = IF_NAMESIZE;
#elif CHIP_SYSTEM_CONFIG_USE_ZEPHYR_NET_IF
static constexpr size_t kMaxIfNameLength = Z_DEVICE_MAX_NAME_LEN;
#elif defined(IFNAMSIZ)
static constexpr size_t kMaxIfNameLength = IFNAMSIZ;
#else
// No constant available here - set some reasonable size
static constexpr size_t kMaxIfNameLength = 33;
#endif

protected:
#if CHIP_SYSTEM_CONFIG_USE_LWIP
struct netif * mCurNetif;
Expand Down
2 changes: 1 addition & 1 deletion src/inet/tests/TestInetCommonPosix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void ShutdownSystemLayer()
#if CHIP_SYSTEM_CONFIG_USE_LWIP
static void PrintNetworkState()
{
char intfName[10];
char intfName[chip::Inet::InterfaceIterator::kMaxIfNameLength];

for (size_t j = 0; j < gNetworkOptions.TapDeviceName.size(); j++)
{
Expand Down
2 changes: 1 addition & 1 deletion src/inet/tests/TestInetEndPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ static void TestInetInterface(nlTestSuite * inSuite, void * inContext)
{
InterfaceIterator intIterator;
InterfaceAddressIterator addrIterator;
char intName[20];
char intName[chip::Inet::InterfaceIterator::kMaxIfNameLength];
InterfaceId intId;
IPAddress addr;
IPPrefix addrWithPrefix;
Expand Down
161 changes: 132 additions & 29 deletions src/lib/mdns/Advertiser_ImplMinimalMdns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,35 +86,58 @@ void LogQuery(const QueryData & data)
ChipLogDetail(Discovery, "%s", logString.c_str());
}

/// Checks if the current interface is powered on
/// and not local loopback.
template <typename T>
bool IsCurrentInterfaceUsable(T & iterator)
{
if (!iterator.IsUp() || !iterator.SupportsMulticast())
{
return false; // not a usable interface
}
char name[chip::Inet::InterfaceIterator::kMaxIfNameLength];
if (iterator.GetInterfaceName(name, sizeof(name)) != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to get interface name.");
return false;
}

if (strncmp(name, "lo", 2) == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a new issue, but doesn't seem right that if I rename my interface to "lollipop" it will be skipped.

Probably the name is the wrong thing to look at altogether, and we should just inspect the address.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could find no better way.

For devices this may not be an issue (since they control their wifi name and it is likely LWIP anyway). Would people rename interfaces on android/iOS?

Added a TODO to auto-create an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just get the address and see if it's ::1 or inside 127.0.0.0/8 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InterfaceIterator does not provide the address only InterfaceAddressIterator does.

Absolutely possible to use AddressIterator instead and do some dedup, but I find it more bothersome than what I would be generally add as a 'fix if you are already touching this'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real fix would probably to fix the interface iterator to be able to return an address iterator for that specified interface. Having 2 types of iterators that both iterate over all interfaces is strange.

{
/// local loopback interface is not usable by MDNS
return false;
}
return true;
}

class AllInterfaces : public ListenIterator
{
private:
public:
AllInterfaces() {}
AllInterfaces() { SkipToFirstValidInterface(); }

bool Next(chip::Inet::InterfaceId * id, chip::Inet::IPAddressType * type) override
{
if (!mIterator.HasCurrent())
{
return false;
}

#if INET_CONFIG_ENABLE_IPV4
if (mState == State::kIpV4)
{
*id = INET_NULL_INTERFACEID;
*id = mIterator.GetInterfaceId();
*type = chip::Inet::kIPAddressType_IPv4;
mState = State::kIpV6;

SkipToFirstValidInterface();
return true;
}
#else
mState = State::kIpV6;
SkipToFirstValidInterface();
#endif

if (!mIterator.HasCurrent())
{
return false;
}

*id = mIterator.GetInterfaceId();
*type = chip::Inet::kIPAddressType_IPv6;
#if INET_CONFIG_ENABLE_IPV4
mState = State::kIpV4;
#endif

for (mIterator.Next(); SkipCurrentInterface(); mIterator.Next())
{
Expand All @@ -128,7 +151,11 @@ class AllInterfaces : public ListenIterator
kIpV4,
kIpV6,
};
#if INET_CONFIG_ENABLE_IPV4
State mState = State::kIpV4;
#else
State mState = State::kIpV6;
#endif
chip::Inet::InterfaceIterator mIterator;

void SkipToFirstValidInterface()
Expand All @@ -149,23 +176,7 @@ class AllInterfaces : public ListenIterator
return false; // nothing to try.
}

if (!mIterator.IsUp() || !mIterator.SupportsMulticast())
{
return true; // not a usable interface
}
char name[64];
if (mIterator.GetInterfaceName(name, sizeof(name)) != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Interface iterator failed to get interface name.");
return true;
}

if (strncmp(name, "lo", 2) == 0)
{
ChipLogDetail(Discovery, "Skipping interface '%s' (assume local loopback)", name);
return true;
}
return false;
return !IsCurrentInterfaceUsable(mIterator);
}
};

Expand Down Expand Up @@ -209,6 +220,15 @@ class AdvertiserMinMdns : public ServiceAdvertiser,
/// allocated memory.
void Clear();

/// Advertise available records configured within the server
///
/// Usable as boot-time advertisement of available SRV records.
void AdvertiseRecords();

/// Determine if advertisement on the specified interface/address is ok given the
/// interfaces on which the mDNS server is listening
bool ShouldAdvertiseOn(const chip::Inet::InterfaceId id, const chip::Inet::IPAddress & addr);

QueryResponderSettings AddAllocatedResponder(Responder * responder)
{
if (responder == nullptr)
Expand Down Expand Up @@ -327,6 +347,9 @@ CHIP_ERROR AdvertiserMinMdns::Start(chip::Inet::InetLayer * inetLayer, uint16_t
ReturnErrorOnFailure(mServer.Listen(inetLayer, &allInterfaces, port));

ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising.");

AdvertiseRecords();

return CHIP_NO_ERROR;
}

Expand Down Expand Up @@ -569,6 +592,86 @@ FullQName AdvertiserMinMdns::GetCommisioningTextEntries(const CommissionAdvertis
}
}

bool AdvertiserMinMdns::ShouldAdvertiseOn(const chip::Inet::InterfaceId id, const chip::Inet::IPAddress & addr)
{
for (unsigned i = 0; i < mServer.GetEndpointCount(); i++)
{
const ServerBase::EndpointInfo & info = mServer.GetEndpoints()[i];

if (info.udp == nullptr)
{
continue;
}

if (info.interfaceId != id)
{
continue;
}

if (info.addressType != addr.Type())
{
continue;
}

return true;
}

return false;
}

void AdvertiserMinMdns::AdvertiseRecords()
{
chip::Inet::InterfaceAddressIterator interfaceAddress;

if (!interfaceAddress.Next())
{
return;
}

for (; interfaceAddress.HasCurrent(); interfaceAddress.Next())
{
if (!IsCurrentInterfaceUsable(interfaceAddress))
{
continue;
}

if (!ShouldAdvertiseOn(interfaceAddress.GetInterfaceId(), interfaceAddress.GetAddress()))
{
continue;
}

chip::Inet::IPPacketInfo packetInfo;

packetInfo.Clear();
packetInfo.SrcAddress = interfaceAddress.GetAddress();
if (interfaceAddress.GetAddress().IsIPv4())
{
BroadcastIpAddresses::GetIpv4Into(packetInfo.DestAddress);
}
else
{
BroadcastIpAddresses::GetIpv6Into(packetInfo.DestAddress);
}
packetInfo.SrcPort = kMdnsPort;
packetInfo.DestPort = kMdnsPort;
packetInfo.Interface = interfaceAddress.GetInterfaceId();

QueryData queryData(QType::PTR, QClass::IN, false /* unicast */);
queryData.SetIsBootAdvertising(true);

mQueryResponder.ClearBroadcastThrottle();

CHIP_ERROR err = mResponseSender.Respond(0, queryData, &packetInfo);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Discovery, "Failed to advertise records: %s", ErrorStr(err));
}
}

// Once all automatic broadcasts are done, allow immediate replies once.
mQueryResponder.ClearBroadcastThrottle();
}

AdvertiserMinMdns gAdvertiser;
} // namespace

Expand Down
13 changes: 12 additions & 1 deletion src/lib/mdns/minimal/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class QueryData
QueryData(const QueryData &) = default;
QueryData & operator=(const QueryData &) = default;

QueryData(QType type, QClass klass, bool unicast) : mType(type), mClass(klass), mAnswerViaUnicast(unicast) {}

QueryData(QType type, QClass klass, bool unicast, const uint8_t * nameStart, const BytesRange & validData) :
mType(type), mClass(klass), mAnswerViaUnicast(unicast), mNameIterator(validData, nameStart)
{}
Expand All @@ -39,6 +41,11 @@ class QueryData
QClass GetClass() const { return mClass; }
bool RequestedUnicastAnswer() const { return mAnswerViaUnicast; }

/// Boot advertisement is an internal query meant to advertise all available
/// services at device startup time.
bool IsBootAdvertising() const { return mIsBootAdvertising; }
void SetIsBootAdvertising(bool isBootAdvertising) { mIsBootAdvertising = isBootAdvertising; }

SerializedQNameIterator GetName() const { return mNameIterator; }

/// Parses a query structure
Expand All @@ -55,7 +62,11 @@ class QueryData
QType mType = QType::ANY;
QClass mClass = QClass::ANY;
bool mAnswerViaUnicast = false;
SerializedQNameIterator mNameIterator; // const since we reuse it
SerializedQNameIterator mNameIterator;

/// Flag as a boot-time internal query. This allows query replies
/// to be built accordingly.
bool mIsBootAdvertising = false;
};

class ResourceData
Expand Down
42 changes: 36 additions & 6 deletions src/lib/mdns/minimal/QueryReplyFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,17 @@ class QueryReplyFilter : public ReplyFilter

bool Accept(QType qType, QClass qClass, FullQName qname) override
{
// resource type is ignored
if ((mQueryData.GetType() != QType::ANY) && (mQueryData.GetType() != qType))
if (!AcceptableQueryType(qType))
{
return false;
}

if ((mQueryData.GetClass() != QClass::ANY) && (mQueryData.GetClass() != qClass))
if (!AcceptableQueryClass(qClass))
{
return false;
}

// path must match
return mIgnoreNameMatch || (mQueryData.GetName() == qname);
return AcceptablePath(qname);
}

/// Ignore qname matches during Accept calls (if set to true, only qtype and qclass are matched).
Expand All @@ -59,9 +57,41 @@ class QueryReplyFilter : public ReplyFilter
return *this;
}

QueryReplyFilter & SetSendingAdditionalItems(bool additional)
{
mSendingAdditionalItems = additional;
return *this;
}

private:
bool AcceptableQueryType(QType qType)
{
if (mSendingAdditionalItems && mQueryData.IsBootAdvertising())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check needed (isn't the 2nd condition true already?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsBootAdvertising is only set during initial boot, not during normal query replies, where as 'sending additional items' is set for any reply processing. I believe they are both needed.

{
return true;
}

return ((mQueryData.GetType() == QType::ANY) || (mQueryData.GetType() == qType));
}

bool AcceptableQueryClass(QClass qClass)
{
return ((mQueryData.GetClass() == QClass::ANY) || (mQueryData.GetClass() == qClass));
}

bool AcceptablePath(FullQName qname)
{
if (mIgnoreNameMatch || mQueryData.IsBootAdvertising())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need IsBootAdvertising instead of just using IgnoreNameMatch ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed to make the intent cleaner by having behaviour depend on query.

The boot-time broadcaster builds a fake query (so that the sender sends replies to such a query) however it does not have access to the underlying filtering used by the mDNS sender.

Alternative would be to propagate flags into the mdns sending and that seeemed to be a bit uglier.

{
return true;
}

return (mQueryData.GetName() == qname);
}

const QueryData & mQueryData;
bool mIgnoreNameMatch = false;
bool mIgnoreNameMatch = false;
bool mSendingAdditionalItems = false;
};

} // namespace Minimal
Expand Down
4 changes: 2 additions & 2 deletions src/lib/mdns/minimal/ResponseSender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ CHIP_ERROR ResponseSender::Respond(uint32_t messageId, const QueryData & query,
const uint64_t kTimeNowMs = chip::System::Platform::Layer::GetClock_MonotonicMS();

QueryReplyFilter queryReplyFilter(query);

QueryResponderRecordFilter responseFilter;

responseFilter.SetReplyFilter(&queryReplyFilter);
Expand Down Expand Up @@ -108,7 +107,8 @@ CHIP_ERROR ResponseSender::Respond(uint32_t messageId, const QueryData & query,
mSendState.SetResourceType(ResourceType::kAdditional);

QueryReplyFilter queryReplyFilter(query);
queryReplyFilter.SetIgnoreNameMatch(true);

queryReplyFilter.SetIgnoreNameMatch(true).SetSendingAdditionalItems(true);

QueryResponderRecordFilter responseFilter;
responseFilter
Expand Down
Loading