mirror of https://github.com/probonopd/MiniDexed
Backport mdnspublisher from Circle (#761)
* add initial mdns responder for rtp-midi aka applemidi * do not reinitialized network * continue publishing mdns after network connection reestablishes * remote mdnsresponder and revert to step48 * disable mdns TTL patching for now * backport mdnspublisher * re-enable build and fix TTLShortpull/762/head^2
parent
3c35244ddb
commit
02b709df31
@ -0,0 +1,345 @@ |
||||
//
|
||||
// mdnspublisher.cpp
|
||||
//
|
||||
// Circle - A C++ bare metal environment for Raspberry Pi
|
||||
// Copyright (C) 2024 R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
#include "mdnspublisher.h" |
||||
#include <circle/sched/scheduler.h> |
||||
#include <circle/net/in.h> |
||||
#include <circle/logger.h> |
||||
#include <circle/util.h> |
||||
#include <assert.h> |
||||
#define MDNS_HOST_GROUP {224, 0, 0, 251} |
||||
#define MDNS_PORT 5353 |
||||
#define MDNS_DOMAIN "local" |
||||
#define RR_TYPE_A 1 |
||||
#define RR_TYPE_PTR 12 |
||||
#define RR_TYPE_TXT 16 |
||||
#define RR_TYPE_SRV 33 |
||||
#define RR_CLASS_IN 1 |
||||
#define RR_CACHE_FLUSH 0x8000 |
||||
LOGMODULE ("mdnspub"); |
||||
CmDNSPublisher::CmDNSPublisher (CNetSubSystem *pNet) |
||||
: m_pNet (pNet), |
||||
m_pSocket (nullptr), |
||||
m_bRunning (FALSE), |
||||
m_pWritePtr (nullptr), |
||||
m_pDataLen (nullptr) |
||||
{ |
||||
SetName ("mdnspub"); |
||||
} |
||||
CmDNSPublisher::~CmDNSPublisher (void) |
||||
{ |
||||
assert (!m_pSocket); |
||||
m_bRunning = FALSE; |
||||
} |
||||
boolean CmDNSPublisher::PublishService (const char *pServiceName, const char *pServiceType, |
||||
u16 usServicePort, const char *ppText[]) |
||||
{ |
||||
if (!m_bRunning) |
||||
{ |
||||
// Let task can run once to initialize
|
||||
CScheduler::Get ()->Yield (); |
||||
if (!m_bRunning) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
} |
||||
assert (pServiceName); |
||||
assert (pServiceType); |
||||
TService *pService = new TService {pServiceName, pServiceType, usServicePort, 0}; |
||||
assert (pService); |
||||
if (ppText) |
||||
{ |
||||
for (unsigned i = 0; i < MaxTextRecords && ppText[i]; i++) |
||||
{ |
||||
pService->ppText[i] = new CString (ppText[i]); |
||||
assert (pService->ppText[i]); |
||||
pService->nTextRecords++; |
||||
} |
||||
} |
||||
m_Mutex.Acquire (); |
||||
// Insert as first element into list
|
||||
TPtrListElement *pElement = m_ServiceList.GetFirst (); |
||||
if (pElement) |
||||
{ |
||||
m_ServiceList.InsertBefore (pElement, pService); |
||||
} |
||||
else |
||||
{ |
||||
m_ServiceList.InsertAfter (nullptr, pService); |
||||
} |
||||
m_Mutex.Release (); |
||||
LOGDBG ("Publish service %s", (const char *) pService->ServiceName); |
||||
m_Event.Set (); // Trigger resent for everything
|
||||
return TRUE; |
||||
} |
||||
boolean CmDNSPublisher::UnpublishService (const char *pServiceName) |
||||
{ |
||||
if (!m_bRunning) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
assert (pServiceName); |
||||
m_Mutex.Acquire (); |
||||
// Find service in the list and remove it
|
||||
TService *pService = nullptr; |
||||
TPtrListElement *pElement = m_ServiceList.GetFirst (); |
||||
while (pElement) |
||||
{ |
||||
pService = static_cast<TService *> (CPtrList::GetPtr (pElement)); |
||||
assert (pService); |
||||
if (pService->ServiceName.Compare (pServiceName) == 0) |
||||
{ |
||||
m_ServiceList.Remove (pElement); |
||||
break; |
||||
} |
||||
pService = nullptr; |
||||
pElement = m_ServiceList.GetNext (pElement); |
||||
} |
||||
m_Mutex.Release (); |
||||
if (!pService) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
LOGDBG ("Unpublish service %s", (const char *) pService->ServiceName); |
||||
if (!SendResponse (pService, TRUE)) |
||||
{ |
||||
LOGWARN ("Send failed"); |
||||
} |
||||
for (unsigned i = 0; i < pService->nTextRecords; i++) |
||||
{ |
||||
delete pService->ppText[i]; |
||||
} |
||||
delete pService; |
||||
return TRUE; |
||||
} |
||||
void CmDNSPublisher::Run (void) |
||||
{ |
||||
assert (m_pNet); |
||||
assert (!m_pSocket); |
||||
m_pSocket = new CSocket (m_pNet, IPPROTO_UDP); |
||||
assert (m_pSocket); |
||||
if (m_pSocket->Bind (MDNS_PORT) < 0) |
||||
{ |
||||
LOGERR ("Cannot bind to port %u", MDNS_PORT); |
||||
delete m_pSocket; |
||||
m_pSocket = nullptr; |
||||
while (1) |
||||
{ |
||||
m_Event.Clear (); |
||||
m_Event.Wait (); |
||||
} |
||||
} |
||||
static const u8 mDNSIPAddress[] = MDNS_HOST_GROUP; |
||||
CIPAddress mDNSIP (mDNSIPAddress); |
||||
if (m_pSocket->Connect (mDNSIP, MDNS_PORT) < 0) |
||||
{ |
||||
LOGERR ("Cannot connect to mDNS host group"); |
||||
delete m_pSocket; |
||||
m_pSocket = nullptr; |
||||
while (1) |
||||
{ |
||||
m_Event.Clear (); |
||||
m_Event.Wait (); |
||||
} |
||||
} |
||||
m_bRunning = TRUE; |
||||
while (1) |
||||
{ |
||||
m_Event.Clear (); |
||||
m_Event.WaitWithTimeout ((TTLShort - 10) * 1000000); |
||||
for (unsigned i = 1; i <= 3; i++) |
||||
{ |
||||
m_Mutex.Acquire (); |
||||
TPtrListElement *pElement = m_ServiceList.GetFirst (); |
||||
while (pElement) |
||||
{ |
||||
TService *pService = |
||||
static_cast<TService *> (CPtrList::GetPtr (pElement)); |
||||
assert (pService); |
||||
if (!SendResponse (pService, FALSE)) |
||||
{ |
||||
LOGWARN ("Send failed"); |
||||
} |
||||
pElement = m_ServiceList.GetNext (pElement); |
||||
} |
||||
m_Mutex.Release (); |
||||
CScheduler::Get ()->Sleep (1); |
||||
} |
||||
} |
||||
} |
||||
boolean CmDNSPublisher::SendResponse (TService *pService, boolean bDelete) |
||||
{ |
||||
assert (pService); |
||||
assert (m_pNet); |
||||
// Collect data
|
||||
static const char Domain[] = "." MDNS_DOMAIN; |
||||
CString ServiceType (pService->ServiceType); |
||||
ServiceType.Append (Domain); |
||||
CString ServiceName (pService->ServiceName); |
||||
ServiceName.Append ("."); |
||||
ServiceName.Append (ServiceType); |
||||
CString Hostname (m_pNet->GetHostname ()); |
||||
Hostname.Append (Domain); |
||||
// Start writing buffer
|
||||
assert (!m_pWritePtr); |
||||
m_pWritePtr = m_Buffer; |
||||
// mDNS Header
|
||||
PutWord (0); // Transaction ID
|
||||
PutWord (0x8400); // Message is a response, Server is an authority for the domain
|
||||
PutWord (0); // Questions
|
||||
PutWord (5); // Answer RRs
|
||||
PutWord (0); // Authority RRs
|
||||
PutWord (0); // Additional RRs
|
||||
// Answer RRs
|
||||
// PTR
|
||||
PutDNSName ("_services._dns-sd._udp.local"); |
||||
PutWord (RR_TYPE_PTR); |
||||
PutWord (RR_CLASS_IN); |
||||
PutDWord (bDelete ? TTLDelete : TTLLong); |
||||
ReserveDataLength (); |
||||
u8 *pServiceTypePtr = m_pWritePtr; |
||||
PutDNSName (ServiceType); |
||||
SetDataLength (); |
||||
// PTR
|
||||
PutCompressedString (pServiceTypePtr); |
||||
PutWord (RR_TYPE_PTR); |
||||
PutWord (RR_CLASS_IN); |
||||
PutDWord (bDelete ? TTLDelete : TTLLong); |
||||
ReserveDataLength (); |
||||
u8 *pServiceNamePtr = m_pWritePtr; |
||||
PutDNSName (ServiceName); |
||||
SetDataLength (); |
||||
// SRV
|
||||
PutCompressedString (pServiceNamePtr); |
||||
PutWord (RR_TYPE_SRV); |
||||
PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); |
||||
PutDWord (bDelete ? TTLDelete : TTLShort); |
||||
ReserveDataLength (); |
||||
PutWord (0); // Priority
|
||||
PutWord (0); // Weight
|
||||
PutWord (pService->usServicePort); |
||||
u8 *pHostnamePtr = m_pWritePtr; |
||||
PutDNSName (Hostname); |
||||
SetDataLength (); |
||||
// A
|
||||
PutCompressedString (pHostnamePtr); |
||||
PutWord (RR_TYPE_A); |
||||
PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); |
||||
PutDWord (TTLShort); |
||||
ReserveDataLength (); |
||||
PutIPAddress (*m_pNet->GetConfig ()->GetIPAddress ()); |
||||
SetDataLength (); |
||||
// TXT
|
||||
PutCompressedString (pServiceNamePtr); |
||||
PutWord (RR_TYPE_TXT); |
||||
PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); |
||||
PutDWord (bDelete ? TTLDelete : TTLLong); |
||||
ReserveDataLength (); |
||||
for (int i = pService->nTextRecords-1; i >= 0; i--) // In reverse order
|
||||
{ |
||||
assert (pService->ppText[i]); |
||||
PutString (*pService->ppText[i]); |
||||
} |
||||
SetDataLength (); |
||||
unsigned nMsgSize = m_pWritePtr - m_Buffer; |
||||
m_pWritePtr = nullptr; |
||||
if (nMsgSize >= MaxMessageSize) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
assert (m_pSocket); |
||||
return m_pSocket->Send (m_Buffer, nMsgSize, MSG_DONTWAIT) == (int) nMsgSize; |
||||
} |
||||
void CmDNSPublisher::PutByte (u8 uchValue) |
||||
{ |
||||
assert (m_pWritePtr); |
||||
if ((unsigned) (m_pWritePtr - m_Buffer) < MaxMessageSize) |
||||
{ |
||||
*m_pWritePtr++ = uchValue; |
||||
} |
||||
} |
||||
void CmDNSPublisher::PutWord (u16 usValue) |
||||
{ |
||||
PutByte (usValue >> 8); |
||||
PutByte (usValue & 0xFF); |
||||
} |
||||
void CmDNSPublisher::PutDWord (u32 nValue) |
||||
{ |
||||
PutWord (nValue >> 16); |
||||
PutWord (nValue & 0xFFFF); |
||||
} |
||||
void CmDNSPublisher::PutString (const char *pValue) |
||||
{ |
||||
assert (pValue); |
||||
size_t nLen = strlen (pValue); |
||||
assert (nLen <= 255); |
||||
PutByte (nLen); |
||||
while (*pValue) |
||||
{ |
||||
PutByte (static_cast<u8> (*pValue++)); |
||||
} |
||||
} |
||||
void CmDNSPublisher::PutCompressedString (const u8 *pWritePtr) |
||||
{ |
||||
assert (m_pWritePtr); |
||||
assert (pWritePtr < m_pWritePtr); |
||||
unsigned nOffset = pWritePtr - m_Buffer; |
||||
assert (nOffset < MaxMessageSize); |
||||
nOffset |= 0xC000; |
||||
PutWord (static_cast<u16> (nOffset)); |
||||
} |
||||
void CmDNSPublisher::PutDNSName (const char *pValue) |
||||
{ |
||||
char Buffer[256]; |
||||
assert (pValue); |
||||
strncpy (Buffer, pValue, sizeof Buffer); |
||||
Buffer[sizeof Buffer-1] = '\0'; |
||||
char *pSavePtr = nullptr; |
||||
char *pToken = strtok_r (Buffer, ".", &pSavePtr); |
||||
while (pToken) |
||||
{ |
||||
PutString (pToken); |
||||
pToken = strtok_r (nullptr, ".", &pSavePtr); |
||||
} |
||||
PutByte (0); |
||||
} |
||||
void CmDNSPublisher::PutIPAddress (const CIPAddress &rValue) |
||||
{ |
||||
u8 Buffer[IP_ADDRESS_SIZE]; |
||||
rValue.CopyTo (Buffer); |
||||
for (unsigned i = 0; i < IP_ADDRESS_SIZE; i++) |
||||
{ |
||||
PutByte (Buffer[i]); |
||||
} |
||||
} |
||||
void CmDNSPublisher::ReserveDataLength (void) |
||||
{ |
||||
assert (!m_pDataLen); |
||||
m_pDataLen = m_pWritePtr; |
||||
assert (m_pDataLen); |
||||
PutWord (0); |
||||
} |
||||
void CmDNSPublisher::SetDataLength (void) |
||||
{ |
||||
assert (m_pDataLen); |
||||
assert (m_pWritePtr); |
||||
assert (m_pWritePtr > m_pDataLen); |
||||
*reinterpret_cast<u16 *> (m_pDataLen) = le2be16 (m_pWritePtr - m_pDataLen - sizeof (u16)); |
||||
m_pDataLen = nullptr; |
||||
} |
@ -0,0 +1,90 @@ |
||||
//
|
||||
// mdnspublisher.h
|
||||
//
|
||||
// Circle - A C++ bare metal environment for Raspberry Pi
|
||||
// Copyright (C) 2024 R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
#ifndef _circle_net_mdnspublisher_h |
||||
#define _circle_net_mdnspublisher_h |
||||
#include <circle/sched/task.h> |
||||
#include <circle/sched/mutex.h> |
||||
#include <circle/sched/synchronizationevent.h> |
||||
#include <circle/net/netsubsystem.h> |
||||
#include <circle/net/socket.h> |
||||
#include <circle/net/ipaddress.h> |
||||
#include <circle/ptrlist.h> |
||||
#include <circle/string.h> |
||||
#include <circle/types.h> |
||||
class CmDNSPublisher : public CTask /// mDNS / Bonjour client task
|
||||
{ |
||||
public: |
||||
static constexpr const char *ServiceTypeAppleMIDI = "_apple-midi._udp"; |
||||
public: |
||||
/// \param pNet Pointer to the network subsystem object
|
||||
CmDNSPublisher (CNetSubSystem *pNet); |
||||
~CmDNSPublisher (void); |
||||
/// \brief Start publishing a service
|
||||
/// \param pServiceName Name of the service to be published
|
||||
/// \param pServiceType Type of the service to be published (e.g. ServiceTypeAppleMIDI)
|
||||
/// \param usServicePort Port number of the service to be published (in host byte order)
|
||||
/// \param ppText Descriptions of the service (terminated with a nullptr, or nullptr itself)
|
||||
/// \return Operation successful?
|
||||
boolean PublishService (const char *pServiceName, |
||||
const char *pServiceType, |
||||
u16 usServicePort, |
||||
const char *ppText[] = nullptr); |
||||
/// \brief Stop publishing a service
|
||||
/// \param pServiceName Name of the service to be unpublished (same as when published)
|
||||
/// \return Operation successful?
|
||||
boolean UnpublishService (const char *pServiceName); |
||||
void Run (void) override; |
||||
private: |
||||
static const unsigned MaxTextRecords = 10; |
||||
static const unsigned MaxMessageSize = 1400; // safe UDP payload in an Ethernet frame
|
||||
static const unsigned TTLShort = 15; // seconds
|
||||
static const unsigned TTLLong = 4500; |
||||
static const unsigned TTLDelete = 0; |
||||
struct TService |
||||
{ |
||||
CString ServiceName; |
||||
CString ServiceType; |
||||
u16 usServicePort; |
||||
unsigned nTextRecords; |
||||
CString *ppText[MaxTextRecords]; |
||||
}; |
||||
boolean SendResponse (TService *pService, boolean bDelete); |
||||
// Helpers for writing to buffer
|
||||
void PutByte (u8 uchValue); |
||||
void PutWord (u16 usValue); |
||||
void PutDWord (u32 nValue); |
||||
void PutString (const char *pValue); |
||||
void PutCompressedString (const u8 *pWritePtr); |
||||
void PutDNSName (const char *pValue); |
||||
void PutIPAddress (const CIPAddress &rValue); |
||||
void ReserveDataLength (void); |
||||
void SetDataLength (void); |
||||
private: |
||||
CNetSubSystem *m_pNet; |
||||
CPtrList m_ServiceList; |
||||
CMutex m_Mutex; |
||||
CSocket *m_pSocket; |
||||
boolean m_bRunning; |
||||
CSynchronizationEvent m_Event; |
||||
u8 m_Buffer[MaxMessageSize]; |
||||
u8 *m_pWritePtr; |
||||
u8 *m_pDataLen; |
||||
}; |
||||
#endif |
Loading…
Reference in new issue