/* * * Copyright (c) 2020-2021 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PacketReporter.h" using namespace chip; namespace { struct Options { bool unicastAnswers = true; uint32_t runtimeMs = 500; uint16_t querySendPort = 5353; uint16_t listenPort = 5388; const char * query = "_services._dns-sd._udp.local"; mdns::Minimal::QType type = mdns::Minimal::QType::ANY; } gOptions; constexpr uint32_t kTestMessageId = 0; constexpr size_t kMdnsMaxPacketSize = 1'024; using namespace chip::ArgParser; constexpr uint16_t kOptionQuery = 'q'; constexpr uint16_t kOptionType = 't'; // non-ascii options have no short option version constexpr uint16_t kOptionListenPort = 0x100; constexpr uint16_t kOptionQueryPort = 0x101; constexpr uint16_t kOptionRuntimeMs = 0x102; constexpr uint16_t kOptionMulticastReplies = 0x103; constexpr uint16_t kOptionTraceTo = 0x104; // Only used for argument parsing. Tracing setup owned by the main loop. chip::CommandLineApp::TracingSetup * tracing_setup_for_argparse = nullptr; bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue) { switch (aIdentifier) { case kOptionListenPort: if (!ParseInt(aValue, gOptions.listenPort)) { PrintArgError("%s: invalid value for listen port: %s\n", aProgram, aValue); return false; } return true; case kOptionQuery: gOptions.query = aValue; return true; case kOptionTraceTo: tracing_setup_for_argparse->EnableTracingFor(aValue); return true; case kOptionType: if (strcasecmp(aValue, "ANY") == 0) { gOptions.type = mdns::Minimal::QType::ANY; } else if (strcasecmp(aValue, "A") == 0) { gOptions.type = mdns::Minimal::QType::A; } else if (strcasecmp(aValue, "AAAA") == 0) { gOptions.type = mdns::Minimal::QType::AAAA; } else if (strcasecmp(aValue, "PTR") == 0) { gOptions.type = mdns::Minimal::QType::PTR; } else if (strcasecmp(aValue, "TXT") == 0) { gOptions.type = mdns::Minimal::QType::TXT; } else if (strcasecmp(aValue, "SRV") == 0) { gOptions.type = mdns::Minimal::QType::SRV; } else if (strcasecmp(aValue, "CNAME") == 0) { gOptions.type = mdns::Minimal::QType::CNAME; } else { PrintArgError("%s: invalid value for query type: %s\n", aProgram, aValue); return false; } return true; case kOptionQueryPort: if (!ParseInt(aValue, gOptions.querySendPort)) { PrintArgError("%s: invalid value for query send port: %s\n", aProgram, aValue); return false; } return true; case kOptionRuntimeMs: if (!ParseInt(aValue, gOptions.runtimeMs)) { PrintArgError("%s: invalid value for runtime ms: %s\n", aProgram, aValue); return false; } return true; case kOptionMulticastReplies: gOptions.unicastAnswers = false; return true; default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); return false; } } OptionDef cmdLineOptionsDef[] = { { "listen-port", kArgumentRequired, kOptionListenPort }, { "query", kArgumentRequired, kOptionQuery }, { "type", kArgumentRequired, kOptionType }, { "query-port", kArgumentRequired, kOptionQueryPort }, { "timeout-ms", kArgumentRequired, kOptionRuntimeMs }, { "multicast-reply", kNoArgument, kOptionMulticastReplies }, { "trace-to", kArgumentRequired, kOptionTraceTo }, {}, }; OptionSet cmdLineOptions = { HandleOptions, cmdLineOptionsDef, "PROGRAM OPTIONS", " --listen-port \n" " The port number to listen on\n" " -q\n" " --query\n" " The query to send\n" " -t\n" " --type\n" " The query type to ask for (ANY/PTR/A/AAAA/SRV/TXT)\n" " --query-port\n" " On what port to multicast the query\n" " --timeout-ms\n" " How long to wait for replies\n" " --multicast-reply\n" " Do not request unicast replies\n" " --trace-to \n" " trace to the given destination (supported: " SUPPORTED_COMMAND_LINE_TRACING_TARGETS ").\n" "\n" }; HelpOptions helpOptions("minimal-mdns-client", "Usage: minimal-mdns-client [options]", "1.0"); OptionSet * allOptions[] = { &cmdLineOptions, &helpOptions, nullptr }; class ReportDelegate : public mdns::Minimal::ServerDelegate { public: void OnQuery(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override { char addr[Inet::IPAddress::kMaxStringLength]; info->SrcAddress.ToString(addr, sizeof(addr)); char ifName[Inet::InterfaceId::kMaxIfNameLength]; VerifyOrDie(info->Interface.GetInterfaceName(ifName, sizeof(ifName)) == CHIP_NO_ERROR); printf("QUERY from: %-15s on port %d, via interface %s\n", addr, info->SrcPort, ifName); Report("QUERY: ", data); } void OnResponse(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override { char addr[Inet::IPAddress::kMaxStringLength]; info->SrcAddress.ToString(addr, sizeof(addr)); char ifName[Inet::InterfaceId::kMaxIfNameLength]; VerifyOrDie(info->Interface.GetInterfaceName(ifName, sizeof(ifName)) == CHIP_NO_ERROR); printf("RESPONSE from: %-15s on port %d, via interface %s\n", addr, info->SrcPort, ifName); Report("RESPONSE: ", data); } private: void Report(const char * prefix, const mdns::Minimal::BytesRange & data) { MdnsExample::PacketReporter reporter(prefix, data); if (!mdns::Minimal::ParsePacket(data, &reporter)) { printf("INVALID PACKET!!!!!!\n"); } } }; class QuerySplitter { public: void Split(const char * query) { mStorage.clear(); mParts.clear(); const char * dot = nullptr; while (nullptr != (dot = strchr(query, '.'))) { mStorage.push_back(std::string(query, dot)); query = dot + 1; } mStorage.push_back(query); for (unsigned i = 0; i < mStorage.size(); i++) { mParts.push_back(mStorage[i].c_str()); } } mdns::Minimal::Query MdnsQuery() const { mdns::Minimal::FullQName qName; qName.nameCount = mParts.size(); qName.names = mParts.data(); return mdns::Minimal::Query(qName); } private: std::vector mParts; std::vector mStorage; }; void BroadcastPacket(mdns::Minimal::ServerBase * server) { System::PacketBufferHandle buffer = System::PacketBufferHandle::New(kMdnsMaxPacketSize); VerifyOrDie(!buffer.IsNull()); QuerySplitter query; query.Split(gOptions.query); mdns::Minimal::QueryBuilder builder(std::move(buffer)); builder.Header().SetMessageId(kTestMessageId); builder.AddQuery(query .MdnsQuery() // .SetClass(mdns::Minimal::QClass::IN) // .SetType(gOptions.type) // .SetAnswerViaUnicast(gOptions.unicastAnswers) // ); if (!builder.Ok()) { printf("Failed to build the question"); return; } if (gOptions.unicastAnswers) { if (server->BroadcastUnicastQuery(builder.ReleasePacket(), gOptions.querySendPort) != CHIP_NO_ERROR) { printf("Error sending\n"); return; } } else { if (server->BroadcastSend(builder.ReleasePacket(), gOptions.querySendPort) != CHIP_NO_ERROR) { printf("Error sending\n"); return; } } } mdns::Minimal::Server<20> gMdnsServer; } // namespace int main(int argc, char ** args) { if (Platform::MemoryInit() != CHIP_NO_ERROR) { printf("FAILED to initialize memory"); return 1; } if (DeviceLayer::PlatformMgr().InitChipStack() != CHIP_NO_ERROR) { printf("FAILED to initialize chip stack"); return 1; } chip::CommandLineApp::TracingSetup tracing_setup; tracing_setup_for_argparse = &tracing_setup; if (!chip::ArgParser::ParseArgs(args[0], argc, args, allOptions)) { return 1; } tracing_setup_for_argparse = nullptr; printf("Running...\n"); ReportDelegate reporter; CHIP_ERROR err; // This forces the global MDNS instance to be loaded in, effectively setting // built in policies for addresses. (void) chip::Dnssd::GlobalMinimalMdnsServer::Instance(); gMdnsServer.SetDelegate(&reporter); { auto endpoints = mdns::Minimal::GetAddressPolicy()->GetListenEndpoints(); err = gMdnsServer.Listen(chip::DeviceLayer::UDPEndPointManager(), endpoints.get(), gOptions.listenPort); if (err != CHIP_NO_ERROR) { printf("Server failed to listen on all interfaces: %s\n", chip::ErrorStr(err)); return 1; } } BroadcastPacket(&gMdnsServer); err = DeviceLayer::SystemLayer().StartTimer( chip::System::Clock::Milliseconds32(gOptions.runtimeMs), [](System::Layer *, void *) { // Close all sockets BEFORE system layer is shut down, otherwise // attempts to free UDP sockets with system layer down will segfault gMdnsServer.Shutdown(); DeviceLayer::PlatformMgr().StopEventLoopTask(); }, nullptr); if (err != CHIP_NO_ERROR) { printf("Failed to create the shutdown timer. Kill with ^C. %" CHIP_ERROR_FORMAT "\n", err.Format()); } DeviceLayer::PlatformMgr().RunEventLoop(); tracing_setup.StopTracing(); DeviceLayer::PlatformMgr().Shutdown(); Platform::MemoryShutdown(); printf("Done...\n"); return 0; }