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 TTLShort
pull/762/head^2
Ömer Şiar Baysal 5 days ago committed by GitHub
parent 3c35244ddb
commit 02b709df31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/build.yml
  2. 2
      src/Makefile
  3. 12
      src/minidexed.cpp
  4. 4
      src/minidexed.h
  5. 345
      src/net/mdnspublisher.cpp
  6. 90
      src/net/mdnspublisher.h

@ -24,7 +24,7 @@ jobs:
# Put git hash in startup message
sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp
# https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505
# sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h
sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h
- name: Install toolchains
run: |
set -ex

@ -11,7 +11,7 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \
mididevice.o udpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o performanceconfig.o perftimer.o \
effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \
net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o
net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o
OPTIMIZE = -O3

@ -64,7 +64,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_bNetworkReady(false),
m_bNetworkInit(false),
m_UDPMIDI (this, pConfig, &m_UI),
//m_pmDNSPublisher (nullptr),
m_pmDNSPublisher (nullptr),
m_bSavePerformance (false),
m_bSavePerformanceNewFile (false),
m_bSetNewPerformance (false),
@ -2232,7 +2232,7 @@ void CMiniDexed::UpdateNetwork()
}
m_UI.DisplayWrite (IPString, "", "TG1", 0, 1); // FIXME: Do not hardcode "TG1" here
/*m_pmDNSPublisher = new CmDNSPublisher (m_pNet);
m_pmDNSPublisher = new CmDNSPublisher (m_pNet);
assert (m_pmDNSPublisher);
//static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // dont bother adding additional data
@ -2241,26 +2241,26 @@ void CMiniDexed::UpdateNetwork()
{
LOGPANIC ("Cannot publish mdns service");
}
*/
m_bNetworkReady = true;
}
if (m_bNetworkReady && !bNetIsRunning)
{
m_bNetworkReady = false;
//m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME);
m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME);
LOGNOTE("Network disconnected.");
}
else if (!m_bNetworkReady && bNetIsRunning)
{
m_bNetworkReady = true;
/*
if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI,
5004))
{
LOGPANIC ("Cannot publish mdns service");
}
*/
LOGNOTE("Network connection reestablished.");
}

@ -43,7 +43,7 @@
#include <circle/net/netsubsystem.h>
#include <wlan/bcm4343.h>
#include <wlan/hostap/wpa_supplicant/wpasupplicant.h>
//#include <circle/net/mdnspublisher.h>
#include "net/mdnspublisher.h"
#include <circle/spinlock.h>
#include "common.h"
#include "effect_mixer.hpp"
@ -341,7 +341,7 @@ private:
bool m_bNetworkInit;
CUDPMIDIDevice m_UDPMIDI;
CFTPDaemon* m_pFTPDaemon;
//CmDNSPublisher *m_pmDNSPublisher;
CmDNSPublisher *m_pmDNSPublisher;
bool m_bSavePerformance;
bool m_bSavePerformanceNewFile;

@ -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…
Cancel
Save