From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Mon, 24 May 2021 11:07:05 +0000 (+0100) Subject: Advertise the _raop._tcp service as well as the _airplay._tcp service. Use a real... X-Git-Tag: 4.1-dev~115 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e1034e11313dc67bd3522246f2bd444cb92059f0;p=thirdparty%2Fshairport-sync.git Advertise the _raop._tcp service as well as the _airplay._tcp service. Use a real MAC as the basis for the device ID. --- diff --git a/common.c b/common.c index ce43d004..4bdb78ab 100644 --- a/common.c +++ b/common.c @@ -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; } } diff --git a/common.h b/common.h index 4d8dcc1d..48769527 100644 --- a/common.h +++ b/common.h @@ -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 diff --git a/mdns.c b/mdns.c index 7eb1f433..073e346c 100644 --- a/mdns.c +++ b/mdns.c @@ -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; diff --git a/mdns.h b/mdns.h index 73c15e8e..30900c78 100644 --- a/mdns.h +++ b/mdns.h @@ -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 *); diff --git a/mdns_avahi.c b/mdns_avahi.c index 4ab991c7..4873639c 100644 --- a/mdns_avahi.c +++ b/mdns_avahi.c @@ -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) { diff --git a/rtsp.c b/rtsp.c index da94052e..ace0e98f 100644 --- a/rtsp.c +++ b/rtsp.c @@ -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 + + +#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 - /* - debug(1,"txt_record:"); - p = txt_records; - while (*p) - debug(1," %s", *p++); - */ - mdns_register(txt_records); + mdns_register(t1,t2); pthread_setcancelstate(oldState, NULL); int acceptfd; diff --git a/shairport.c b/shairport.c index 89fa5858..b2d8886c 100644 --- a/shairport.c +++ b/shairport.c @@ -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