diff --git a/package-lock.json b/package-lock.json
index c9a540dec..d8665b552 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"license": "Apache-2.0",
"dependencies": {
"@homebridge/ciao": "^1.1.5",
- "@homebridge/dbus-native": "^0.4.2",
+ "@homebridge/dbus-native": "^0.5.0",
"bonjour-hap": "~3.6.3",
"debug": "^4.3.4",
"fast-srp-hap": "2.0.4",
@@ -676,9 +676,9 @@
}
},
"node_modules/@homebridge/dbus-native": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.4.2.tgz",
- "integrity": "sha512-rg6DUg6xOttzn73HA1+3G2o1ezRj0+DzPMEJqasrpq7FcAxMcTyOZ96GfcDN4pLUz62hMuywIeVZ4F6cc/g6Ig==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.5.0.tgz",
+ "integrity": "sha512-ei0jyHE/uNDl/6D6heRwsqnESrrXuSlfp+xlwGfg3mo1OqhKvyb/Kp73uxQyOJ3f1T1ocLSyA5uzoR1AbfaXIQ==",
"dependencies": {
"@homebridge/long": "^5.2.1",
"@homebridge/put": "~0.0.8",
@@ -6792,9 +6792,9 @@
}
},
"@homebridge/dbus-native": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.4.2.tgz",
- "integrity": "sha512-rg6DUg6xOttzn73HA1+3G2o1ezRj0+DzPMEJqasrpq7FcAxMcTyOZ96GfcDN4pLUz62hMuywIeVZ4F6cc/g6Ig==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.5.0.tgz",
+ "integrity": "sha512-ei0jyHE/uNDl/6D6heRwsqnESrrXuSlfp+xlwGfg3mo1OqhKvyb/Kp73uxQyOJ3f1T1ocLSyA5uzoR1AbfaXIQ==",
"requires": {
"@homebridge/long": "^5.2.1",
"@homebridge/put": "~0.0.8",
diff --git a/package.json b/package.json
index 5cb8724af..6d698404f 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
],
"dependencies": {
"@homebridge/ciao": "^1.1.5",
- "@homebridge/dbus-native": "^0.4.2",
+ "@homebridge/dbus-native": "^0.5.0",
"bonjour-hap": "~3.6.3",
"debug": "^4.3.4",
"fast-srp-hap": "2.0.4",
diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts
index 6b3661c7d..3febb9ade 100644
--- a/src/lib/Advertiser.ts
+++ b/src/lib/Advertiser.ts
@@ -2,7 +2,7 @@
///
import ciao, { CiaoService, MDNSServerOptions, Responder, ServiceEvent, ServiceTxt, ServiceType } from "@homebridge/ciao";
import { InterfaceName, IPAddress } from "@homebridge/ciao/lib/NetworkManager";
-import dbus, { DBusInterface, MessageBus } from "@homebridge/dbus-native";
+import dbus, { DBusInterface, InvokeError, MessageBus } from "@homebridge/dbus-native";
import assert from "assert";
import bonjour, { BonjourHAP, BonjourHAPService, MulticastOptions } from "bonjour-hap";
import crypto from "crypto";
@@ -278,6 +278,25 @@ function messageBusConnectionResult(bus: MessageBus): Promise {
});
}
+export class DBusInvokeError extends Error {
+ readonly errorName: string;
+
+ constructor(errorObject: InvokeError) {
+ super();
+
+ Object.setPrototypeOf(this, DBusInvokeError.prototype);
+
+ this.name = "DBusInvokeError";
+
+ this.errorName = errorObject.name;
+
+ if (Array.isArray(errorObject.message) && errorObject.message.length === 1) {
+ this.message = errorObject.message[0];
+ } else {
+ this.message = errorObject.message.toString();
+ }
+ }
+}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function dbusInvoke( bus: MessageBus, destination: string, path: string, dbusInterface: string, member: string, others?: any): Promise {
@@ -290,10 +309,9 @@ function dbusInvoke( bus: MessageBus, destination: string, path: string, dbusInt
...(others || {}),
};
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- bus.invoke(command, (err: any, result: any) => {
+ bus.invoke(command, (err, result) => {
if (err) {
- reject(new Error(`dbusInvoke error: ${JSON.stringify(err)}`));
+ reject(new DBusInvokeError(err));
} else {
resolve(result);
}
@@ -504,6 +522,13 @@ export class AvahiAdvertiser extends EventEmitter implements Advertiser {
type ResolvedServiceTxt = Array>;
+const RESOLVED_PERMISSIONS_ERRORS = [
+ "org.freedesktop.DBus.Error.AccessDenied",
+ "org.freedesktop.DBus.Error.AuthFailed",
+ "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired",
+];
+
+
/**
* Advertiser based on the systemd-resolved D-Bus library.
* For docs on the interface, see: https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html
@@ -547,18 +572,27 @@ export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
debug(`Starting to advertise '${this.accessoryInfo.displayName}' using systemd-resolved backend!`);
- this.path = await ResolvedAdvertiser.resolvedInvoke(this.bus, "RegisterService", {
- body: [
- this.accessoryInfo.displayName, // name
- this.accessoryInfo.displayName, // name_template
- "_hap._tcp", // type
- this.port, // service_port
- 0, // service_priority
- 0, // service_weight
- [this.createTxt()], // txt_datas
- ],
- signature: "sssqqqaa{say}",
- });
+ try {
+ this.path = await ResolvedAdvertiser.managerInvoke(this.bus, "RegisterService", {
+ body: [
+ this.accessoryInfo.displayName, // name
+ this.accessoryInfo.displayName, // name_template
+ "_hap._tcp", // type
+ this.port, // service_port
+ 0, // service_priority
+ 0, // service_weight
+ [this.createTxt()], // txt_datas
+ ],
+ signature: "sssqqqaa{say}",
+ });
+ } catch (error) {
+ if (error instanceof DBusInvokeError) {
+ if (RESOLVED_PERMISSIONS_ERRORS.includes(error.errorName)) {
+ error.message = `Permissions issue. See https://homebridge.io/w/mDNS-Options for more info. ${error.message}`;
+ }
+ }
+ throw error;
+ }
}
public async updateAdvertisement(silent?: boolean): Promise {
@@ -580,7 +614,7 @@ export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
if (this.path) {
try {
- await ResolvedAdvertiser.resolvedInvoke(this.bus, "UnregisterService", {
+ await ResolvedAdvertiser.managerInvoke(this.bus, "UnregisterService", {
body: [this.path],
signature: "o",
});
@@ -616,7 +650,7 @@ export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
try {
// Ensure that systemd-resolved is accessible.
- await this.resolvedInvoke(bus, "ResolveHostname", {
+ await this.managerInvoke(bus, "ResolveHostname", {
body: [0, "127.0.0.1", 0, 0],
signature: "isit",
});
@@ -626,6 +660,30 @@ export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
return false;
}
+ try {
+ const mdnsStatus = await this.resolvedInvoke(
+ bus,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ {
+ body: ["org.freedesktop.resolve1.Manager", "MulticastDNS"],
+ signature: "ss",
+ },
+ );
+
+ if (mdnsStatus[0][0].type !== "s") {
+ throw new Error("Invalid type for MulticastDNS");
+ }
+
+ if (mdnsStatus[1][0] !== "yes" ) {
+ debug("systemd-resolved/DBus classified unavailable because MulticastDNS is not enabled!");
+ return false;
+ }
+ } catch (error) {
+ debug("systemd-resolved/DBus classified unavailable due to failure checking system status: " + error);
+ return false;
+ }
+
return true;
} finally {
bus.connection.stream.destroy();
@@ -633,14 +691,19 @@ export class ResolvedAdvertiser extends EventEmitter implements Advertiser {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- private static resolvedInvoke(bus: MessageBus, member: string, others?: any): Promise {
+ private static resolvedInvoke(bus: MessageBus, dbusInterface: string, member: string, others?: any): Promise {
return dbusInvoke(
bus,
"org.freedesktop.resolve1",
"/org/freedesktop/resolve1",
- "org.freedesktop.resolve1.Manager",
+ dbusInterface,
member,
others,
);
}
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private static managerInvoke(bus: MessageBus, member: string, others?: any): Promise {
+ return this.resolvedInvoke(bus, "org.freedesktop.resolve1.Manager", member, others);
+ }
}
diff --git a/src/types/dbus-native.d.ts b/src/types/dbus-native.d.ts
index bca3b1579..cb02c35d3 100644
--- a/src/types/dbus-native.d.ts
+++ b/src/types/dbus-native.d.ts
@@ -4,11 +4,17 @@ declare module "@homebridge/dbus-native" {
function systemBus(): MessageBus;
+ export class InvokeError {
+ name: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ message: any;
+ }
+
export class MessageBus {
connection: BusConnection;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any
- public invoke(message: any, callback: any): void;
+ public invoke(message: any, callback: (error: InvokeError | undefined, value: any) => void): void;
public getService(name: string): DBusService;
}