Advertise the _raop._tcp service as well as the _airplay._tcp service. Use a real MAC as the basis for the device ID.

This commit is contained in:
Mike Brady
2021-05-24 12:07:05 +01:00
parent 3143e9983a
commit e1034e1131
7 changed files with 173 additions and 80 deletions
+12 -12
View File
@@ -1609,9 +1609,9 @@ void pthread_cleanup_debug_mutex_unlock(void *arg) { pthread_mutex_unlock((pthre
char *get_version_string() {
char *version_string = malloc(1024);
if (version_string) {
if (version_string) {
#ifdef CONFIG_USE_GIT_VERSION_STRING
if (git_version_string[0] != '\0')
if (git_version_string[0] != '\0')
strcpy(version_string, git_version_string);
else
#endif
@@ -1939,29 +1939,29 @@ int32_t mod32Difference(uint32_t a, uint32_t b) {
return result;
}
char *get_device_id() {
char *response = NULL;
int get_device_id(uint8_t *id, int int_length) {
int response = 0;
struct ifaddrs *ifaddr = NULL;
struct ifaddrs *ifa = NULL;
int i = 0;
uint8_t *t = id;
for (i = 0; i < int_length ; i++) {
*t++ = 0;
}
if (getifaddrs(&ifaddr) == -1) {
debug(1, "getifaddrs");
response = -1;
} else {
t = id;
int found = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) {
char obf[256] = {0};
char *obfp = obf;
struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr;
if ((strcmp(ifa->ifa_name, "lo") != 0) && (found == 0)) {
for (i = 0; i < s->sll_halen; i++) {
snprintf(obfp, 4, "%02x:", s->sll_addr[i]);
obfp += 3;
for (i = 0; ((i < s->sll_halen) && (i < int_length)); i++) {
*t++ = s->sll_addr[i];
}
obfp -= 1;
*obfp = 0;
response = strdup(obf);
found = 1;
}
}
+10 -2
View File
@@ -209,7 +209,9 @@ typedef struct {
// char *errfile;
char *configfile;
char *regtype; // The regtype is the service type followed by the protocol, separated by a dot, by
// default “_raop._tcp.”.
// default “_raop._tcp.” for AirPlay 1.
char *regtype2; // The regtype is the service type followed by the protocol, separated by a dot, by
// default “_raop._tcp.” for AirPlay 2.
char *interface; // a string containg the interface name, or NULL if nothing specified
int interface_index; // only valid if the interface string is non-NULL
double audio_backend_buffer_desired_length; // this will be the length in seconds of the
@@ -489,7 +491,13 @@ void *memdup(const void *mem, size_t size);
// negative otherwise
int32_t mod32Difference(uint32_t a, uint32_t b);
char *get_device_id();
int get_device_id(uint8_t *id, int int_length);
#ifdef CONFIG_USE_GIT_VERSION_STRING
extern char git_version_string[];
#endif
#endif // _COMMON_H
+6 -7
View File
@@ -64,17 +64,15 @@ static mdns_backend *mdns_backends[] = {
#endif
NULL};
void mdns_register(char **txt_records) {
char *mdns_service_name = alloca(strlen(config.service_name) + 14);
char *p = mdns_service_name;
#ifndef CONFIG_AIRPLAY_2
void mdns_register(char **txt_records, char **secondary_txt_records) {
char *ap1_service_name = alloca(strlen(config.service_name) + 14);
char *p = ap1_service_name;
int i;
for (i = 0; i < 6; i++) {
snprintf(p, 3, "%02X", config.hw_addr[i]);
p += 2;
}
*p++ = '@';
#endif
strcpy(p, config.service_name);
mdns_backend **b = NULL;
@@ -83,7 +81,7 @@ void mdns_register(char **txt_records) {
for (b = mdns_backends; *b; b++) {
if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
continue;
int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records);
int error = (*b)->mdns_register(ap1_service_name, config.service_name, config.port, txt_records, secondary_txt_records);
if (error >= 0) {
config.mdns = *b;
}
@@ -93,8 +91,9 @@ void mdns_register(char **txt_records) {
if (*b == NULL)
warn("%s mDNS backend not found");
} else {
// default -- pick the first back end
for (b = mdns_backends; *b; b++) {
int error = (*b)->mdns_register(mdns_service_name, config.port, txt_records);
int error = (*b)->mdns_register(ap1_service_name, config.service_name, config.port, txt_records, secondary_txt_records);
if (error >= 0) {
config.mdns = *b;
break;
+2 -2
View File
@@ -8,7 +8,7 @@
extern int mdns_pid;
void mdns_unregister(void);
void mdns_register(char **txt_records);
void mdns_register(char **txt_records, char **secondary_txt_records);
void mdns_dacp_monitor_start();
void mdns_dacp_monitor_stop(void);
void mdns_dacp_monitor_set_id(const char *dacp_id);
@@ -17,7 +17,7 @@ void mdns_ls_backends(void);
typedef struct {
char *name;
int (*mdns_register)(char *apname, int port, char **txt_records);
int (*mdns_register)(char *ap1name, char *ap2name, int port, char **txt_records, char **secondary_txt_records);
void (*mdns_unregister)(void);
void (*mdns_dacp_monitor_start)();
void (*mdns_dacp_monitor_set_id)(const char *);
+34 -9
View File
@@ -62,6 +62,7 @@ typedef struct {
dacp_browser_struct private_dbs;
AvahiStringList *text_record_string_list = NULL;
AvahiStringList *ap2_text_record_string_list = NULL;
// static AvahiServiceBrowser *sb = NULL;
static AvahiClient *client = NULL;
@@ -71,6 +72,8 @@ static AvahiThreadedPoll *tpoll = NULL;
// static AvahiThreadedPoll *service_poll = NULL;
static char *service_name = NULL;
char *ap2_service_name = NULL;
static int port = 0;
static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface,
@@ -187,7 +190,7 @@ static void egroup_callback(AvahiEntryGroup *g, AvahiEntryGroupState state,
/* A service name collision with a remote service
* happened. Let's pick a new name */
debug(2, "avahi name collision -- look for another");
debug(1, "avahi name collision -- look for another");
n = avahi_alternative_service_name(service_name);
if (service_name)
avahi_free(service_name);
@@ -238,10 +241,16 @@ static void register_service(AvahiClient *c) {
selected_interface = config.interface_index;
else
selected_interface = AVAHI_IF_UNSPEC;
if (text_record_string_list) {
if (ap2_text_record_string_list) {
ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
ap2_service_name, config.regtype2, NULL, NULL, port,
ap2_text_record_string_list);
}
if ((ret == 0) && (text_record_string_list)) {
ret = avahi_entry_group_add_service_strlst(group, selected_interface, AVAHI_PROTO_UNSPEC, 0,
service_name, config.regtype, NULL, NULL, port,
text_record_string_list);
}
if (ret == 0) {
ret = avahi_entry_group_commit(group);
debug(2, "avahi: avahi_entry_group_commit %d", ret);
@@ -252,9 +261,6 @@ static void register_service(AvahiClient *c) {
} else {
debug(1, "avahi: unexpected positive return");
}
} else {
debug(1, "Can't find a valid text_record_string_list");
}
}
}
@@ -312,12 +318,17 @@ static void client_callback(AvahiClient *c, AvahiClientState state,
}
}
static int avahi_register(char *srvname, int srvport, char **txt_records) {
static int avahi_register(char *ap1name, char *ap2name, int srvport, char **txt_records,char **secondary_txt_records) {
// debug(1, "avahi_register.");
service_name = strdup(srvname);
service_name = strdup(ap1name);
if (ap2name != NULL)
ap2_service_name = strdup(ap2name);
text_record_string_list = avahi_string_list_new_from_array((const char **)txt_records, -1);
if (secondary_txt_records != NULL)
ap2_text_record_string_list = avahi_string_list_new_from_array((const char **)secondary_txt_records, -1);
port = srvport;
int err;
@@ -362,15 +373,29 @@ static void avahi_unregister(void) {
if (service_name) {
debug(2, "avahi: free the service name.");
free(service_name);
service_name = NULL;
} else
debug(1, "avahi attempt to free NULL service name");
if (ap2_service_name) {
debug(2, "avahi: free the ap2 service_name.");
free(ap2_service_name);
ap2_service_name = NULL;
}
if (text_record_string_list) {
debug(2, "avahi free text_record_string_list");
avahi_string_list_free(text_record_string_list);
}
text_record_string_list = NULL;
} else
debug(1, "avahi attempt to free NULL text_record_string_list");
service_name = NULL;
if (ap2_text_record_string_list) {
debug(2, "avahi free ap_2text_record_string_list");
avahi_string_list_free(ap2_text_record_string_list);
ap2_text_record_string_list = NULL;
}
}
void avahi_dacp_monitor_start(void) {
+74 -37
View File
@@ -696,7 +696,7 @@ int msg_handle_line(rtsp_message **pmsg, char *line) {
*p = 0;
p += 2;
msg_add_header(msg, line, p);
debug(3, " %s: %s.", line, p);
// debug(3, " %s: %s.", line, p);
return -1;
} else {
char *cl = msg_get_header(msg, "Content-Length");
@@ -1714,8 +1714,8 @@ void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *
resp->respcode = 200;
msg_add_header(resp, "Public",
"ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER, GET");
"PAUSE, FLUSH, FLUSHBUFFERED, TEARDOWN, "
"OPTIONS, POST, GET, PUT");
}
void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req,
@@ -3414,7 +3414,7 @@ static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
char *hdr = msg_get_header(req, "Apple-Challenge");
if (!hdr)
return;
debug(1,"Apple Challenge");
SOCKADDR fdsa;
socklen_t sa_len = sizeof(fdsa);
getsockname(fd, (struct sockaddr *)&fdsa, &sa_len);
@@ -4084,50 +4084,58 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
maxfd = sockfd[i];
}
char **t1; // ap1 test records
char **t2; // two text records
t1 = NULL;
t2 = NULL;
// make up a txt record
char *txt_records[64];
char **p = txt_records;
t1 = p; // first set of text records
// make up a firmware version
char firmware_version[64];
#ifdef CONFIG_USE_GIT_VERSION_STRING
if (git_version_string[0] != '\0')
snprintf(firmware_version, sizeof(firmware_version), "fv=%s",git_version_string);
else
#endif
snprintf(firmware_version, sizeof(firmware_version), "fv=%s",PACKAGE_VERSION);
#ifdef CONFIG_AIRPLAY_2
*p++ = "srcvers=366.0";
char deviceIdString[64];
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
*p++ = deviceIdString;
// features is a 64 bit number, least significant 32 bits
char featuresString[64];
char ap1_featuresString[64];
uint64_t features_hi = config.airplay_features;
features_hi = (features_hi >> 32) & 0xffffffff;
uint64_t features_lo = config.airplay_features;
features_lo = features_lo & 0xffffffff;
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIx64 ",0x%" PRIx64 "",
snprintf(ap1_featuresString, sizeof(ap1_featuresString), "ft=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
*p++ = featuresString;
char statusflagsString[32];
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIx32,
config.airplay_statusflags);
*p++ = statusflagsString;
*p++ = "protovers=1.1";
*p++ = "acl=0";
*p++ = "rsf=0x0";
*p++ = "fv=p20.78000.12";
*p++ = "model=SPS";
char piString[64];
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
*p++ = piString;
char gidString[64];
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
*p++ = gidString;
*p++ = "gcgl=0";
char pkString[128];
snprintf(pkString, sizeof(pkString), "pk=");
pkString_make(pkString + strlen("pk="), sizeof(pkString) - strlen("pk="),
config.airplay_device_id);
// the ap1 text record is different if it is set up for ap2
*p++ = "cn=0,1";
*p++ = "da=true";
*p++ = "et=0,4";
*p++ = ap1_featuresString;
*p++ = firmware_version;
*p++ = "md=2";
*p++ = "am=SPS";
*p++ = "sf=0x4";
*p++ = "tp=UDP";
*p++ = "vn=65537";
*p++ = "vs=366.0";
*p++ = pkString;
*p++ = NULL;
#else
// here, just replicate what happens in mdns.h when using those #defines
*p++ = "sf=0x4";
*p++ = "fv=76400.10";
*p++ = firmware_version;
*p++ = "am=ShairportSync";
*p++ = "vs=105.1";
*p++ = "tp=TCP,UDP";
@@ -4146,23 +4154,52 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) {
*p++ = "ek=1";
*p++ = "cn=0,1";
*p++ = "ch=2";
*p++ = "am=ShairportSync";
*p++ = "txtvers=1";
if (config.password == 0)
*p++ = "pw=false";
else
*p++ = "pw=true";
*p++ = NULL;
#endif
/*
debug(1,"txt_record:");
p = txt_records;
while (*p)
debug(1," %s", *p++);
*/
mdns_register(txt_records);
#ifdef CONFIG_AIRPLAY_2
// make up a secondary set of text records
char *secondary_txt_records[64];
p = secondary_txt_records;
t2 = p; // second set of text records in AirPlay 2 only
*p++ = "srcvers=366.0";
char deviceIdString[64];
snprintf(deviceIdString, sizeof(deviceIdString), "deviceid=%s", config.airplay_device_id);
*p++ = deviceIdString;
char featuresString[64];
snprintf(featuresString, sizeof(featuresString), "features=0x%" PRIX64 ",0x%" PRIX64 "",
features_lo, features_hi);
*p++ = featuresString;
char statusflagsString[32];
snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIX32,
config.airplay_statusflags);
*p++ = statusflagsString;
*p++ = "protovers=1.1";
*p++ = "acl=0";
*p++ = "rsf=0x0";
*p++ = firmware_version;
*p++ = "model=SPS";
char piString[64];
snprintf(piString, sizeof(piString), "pi=%s", config.airplay_pi);
*p++ = piString;
char gidString[64];
snprintf(gidString, sizeof(gidString), "gid=%s", config.airplay_gid);
*p++ = gidString;
*p++ = "gcgl=0";
*p++ = pkString;
*p++ = NULL;
#endif
mdns_register(t1,t2);
pthread_setcancelstate(oldState, NULL);
int acceptfd;
+35 -11
View File
@@ -1246,10 +1246,10 @@ int parse_options(int argc, char **argv) {
/* if the regtype hasn't been set, do it now */
if (config.regtype == NULL)
#ifdef CONFIG_AIRPLAY_2
config.regtype = strdup("_airplay._tcp");
#else
config.regtype = strdup("_raop._tcp");
#ifdef CONFIG_AIRPLAY_2
if (config.regtype2 == NULL)
config.regtype2 = strdup("_airplay._tcp");
#endif
if (tdebuglev != 0)
@@ -1416,6 +1416,11 @@ void exit_function() {
if (config.regtype)
free(config.regtype);
#ifdef CONFIG_AIRPLAY_2
if (config.regtype2)
free(config.regtype2);
#endif
#ifdef CONFIG_LIBDAEMON
if (this_is_the_daemon_process) {
@@ -1515,6 +1520,9 @@ int main(int argc, char **argv) {
// set defaults
// get a device id -- the first non-local MAC address
get_device_id((uint8_t *)&config.hw_addr, 6);
// get the endianness
union {
uint32_t u32;
@@ -1582,15 +1590,27 @@ int main(int argc, char **argv) {
// required (transient pairing), no Homekit access control 0x204: Audio cable attached,
// OneTimePairingRequired 0x604: Audio cable attached, OneTimePairingRequired, allow Homekit
// access control
// note: 0x300401F4A00 works but with weird delays and stuff
config.airplay_statusflags = 0x4;
// Set to NULL to work with transient pairing
config.airplay_pin = NULL;
// get a device id -- the first non-local MAC address, not necessarily the one in use
config.airplay_device_id = get_device_id();
if (config.airplay_device_id) {
debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
} else
debug(1, "Started in Airplay 2 mode!");
// use the config.hw_addr to generated an airplay_device_id
{
char obf[256] = {0};
char *obfp = obf;
int i;
for (i = 0; i < 6; i++) {
snprintf(obfp, 4, "%02X:", config.hw_addr[i]);
obfp += 3;
}
obfp -= 1;
*obfp = 0;
config.airplay_device_id = strdup(obf);
}
debug(1, "Started in Airplay 2 mode on device \"%s\"!", config.airplay_device_id);
// now generate a UUID
// from https://stackoverflow.com/questions/51053568/generating-a-random-uuid-in-c
@@ -2014,12 +2034,13 @@ int main(int argc, char **argv) {
debug(1, "loudness is %d.", config.loudness);
debug(1, "loudness reference level is %f", config.loudness_reference_volume_db);
uint8_t ap_md5[16];
#ifdef CONFIG_SOXR
pthread_create(&soxr_time_check_thread, NULL, &soxr_time_check, NULL);
#endif
/*
uint8_t ap_md5[16];
#ifdef CONFIG_OPENSSL
MD5_CTX ctx;
MD5_Init(&ctx);
@@ -2047,7 +2068,10 @@ int main(int argc, char **argv) {
md5_update(&tctx, (unsigned char *)config.service_name, strlen(config.service_name));
md5_finish(&tctx, ap_md5);
#endif
memcpy(config.hw_addr, ap_md5, sizeof(config.hw_addr));
*/
#ifdef CONFIG_METADATA
metadata_init(); // create the metadata pipe if necessary
#endif