bobvanderlinden / node-machinetalk

A client-side Node API for remotely controlling/monitoring Machinekit instances through Machinetalk
GNU Lesser General Public License v3.0
2 stars 4 forks source link

node-mdns is broken #17

Open machinekoder opened 8 years ago

machinekoder commented 8 years ago

node-mdns uses the mDNSResponder interface which is pretty much broken on Linux. It looks like no proper mDNS implementation (at least not discovery-wise) for nodejs exists at the moment. I suggest making service discovery modular so that it can be used from nodejs and others (e.g. using avahi oder jdns). Idea: create a service discovery service (e.g. with the Python avahi bindings) and bind it to a well known ipc socket -> all machinetalk components can use it independent on the language

*** WARNING *** The program 'nodejs' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=nodejs>
*** WARNING *** The program 'nodejs' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=nodejs&f=DNSServiceRegister>
events.js:141
      throw er; // Unhandled 'error' event
      ^

Error: getaddrinfo -3008
    at errnoException (/home/alexander/projects/gsl/machinetalk/node_modules/mdns/lib/resolver_sequence_tasks.js:199:11)
    at getaddrinfo_complete (/home/alexander/projects/gsl/machinetalk/node_modules/mdns/lib/resolver_sequence_tasks.js:112:10)
    at GetAddrInfoReqWrap.oncomplete (/home/alexander/projects/gsl/machinetalk/node_modules/mdns/lib/resolver_sequence_tasks.js:120:9)
machinekoder commented 8 years ago

Tested https://github.com/mdns-js/node-mdns-js and it works. Good alternative until there is service discovery service.

bobvanderlinden commented 8 years ago

node-mdns does have service discovery. I've tried it between beaglebone (Debian) and laptop (NixOS) as well as host (ArchLinux) and virtualbox (Debian). The problem I had with node-mdns were:

  1. It requires compiling C/C++. This is annoying for windows users.
  2. It requires the bonjour compatibility library for avahi to do discovery (libdnssd). This is not a required dependency for avahi, so this libary needs to be installed manually.
  3. It requires avahi dns resolving to be installed on the system (in /etc/nsswitch.conf). This could be optional if the application explicitly resolves addresses through mdns (which could be the case for node-mdns-js), but it's less practical as you cannot let (for instance) zeromq do the resolving of its addresses.
  4. There seems to be a bug in node-mdns with regards to ipv4 and ipv6 that results in (amongst others) the error you describe. I noticed this when using it inside a Docker instance, but others have found it when using it on a Raspberry Pi. I've made a PR (https://github.com/agnat/node_mdns/pull/149), but that does a quick-fix, whereas an actual fix is required.

That last one applies to your problem I think. The warnings are just there because node-mdns uses the Bonjour compatiblity library on Avahi (it does this because it already uses Bonjour for Mac OSX). It does discover machines, but it doesn't resolve their domain names correctly on some systems without the fix (https://github.com/agnat/node_mdns/pull/149). The fix was found by folks trying to make node-mdns work on Raspberry Pi. I ran into the problem when running inside Docker. I think it's triggered in these environments because they only support ipv4, whereas node-mdns tries to resolve addresses on both ipv4 and ipv6.

As for node-mdns-js. I've tried it too: https://github.com/bobvanderlinden/node-machinetalk/tree/mdnsjs However, what I did not like was:

  1. It doesn't have system-wide caching like Avahi and Bonjour have. For instance, when you start Machinekit before starting the node app, node-mdns-js couldn't find it. When you start Machinekit while the node app is running, it does find it. node-mdns uses Avahi and Avahi caches all discovery states: it instantly knows all machines and services when we need them. I'm not sure whether node-mdns-js could do some kind of broadcast to ask for all machinekit services, but I was at the time not able to make this work.
  2. In addition I was a bit afraid that the library and any existing mdns daemon already running (avahi or Bonjour) would bite eachother.

So, it's still a good option to investigate. Mainly I'd like to use node-mdns-js, because compilation on Windows is really annoying and it doesn't need any existing mdns daemon (no Bonjour on Windows).

bobvanderlinden commented 8 years ago

Oh, this is also related to #11.

zhivko commented 8 years ago

Is this the reason why I get "Machines:" box empty with BBM machinkeit running on same subnet?

machinekoder commented 8 years ago

node-mdns is in my opinion not worth fixing unless you want to completely rewrite it. Either we go with a discovery service or one has to build a node module based on jdns (https://github.com/psi-im/jdns) which is a proper cross-platform implementation.

Letting the OS resolve the hostname does not work on Windows since it does not support .local hostnames. So hostname lookup using mDNS needs to be done in the service itself.

bobvanderlinden commented 8 years ago

@zhivko If you do not get errors like mentioned above, it's is a different issue. If you're on Linux, you can check what services Avahi has discovered using avahi-browse -a.

@strahlex As for node_mdns, can you explain why it is not worth fixing?

Hmm, I was under the impression Bonjour was needed on Windows, which would do the resolving. I am not that familiar with the mdns protocol itself, but it seems we need discovery (passively listening for advertisements of mdns) and resolving/lookup (actively asking for addresses). Is that correct? If so, then I'd prefer using a pure JS solution or at least a solution that has the least amount of dependencies. That way Windows will have less problems compiling. node-mdns-js seems to do only the discovery part. multicast-dns seems to be able to resolve addresses. The combination would fit our purposes (I think/hope). Using jdns is possible, but I'm a bit worried it would make installation harder for some.

machinekoder commented 8 years ago

node-mdns is based on the Apple mDNSResolver library which is not a very good mDNS implementation, especially considering that node-machinetalk will mostly run on Linux hosts. Unless you fix the dependency on mDNSResolver you will not fix node-mdns.

One thing people always confuse is mDNS, Zeroconf and Bonjour.

mDNS is multicast DNS -> meaning DNS lookups based on multicast, how well multicast works strongly depends on your networking hardware, unfortunately a weak point that one should blame all these ISPs installing cheap routers in home network for.

mDNS is not purely passive, a good implementation queries the multicast group when started, newcomers to a multicast group announce themselves, queries are refreshed after a certain timeout (depending on networking hardware)

mDNS does not resolve any services it is just DNS over multicast, queries retourn pointers (A, AAA, SRV, PTR, ...)

DNS-SD is service discovery over DNS, meaning it works over multicast DNS and unicast DNS

Zeroconf is mDNS + DNS-SD -> node-mdns is actually a Zeroconf implementation

Bonjour is Apples name for Zeroconf

A DNS-SD service discovery consists of multiple DNS queries. I think 3 in total. Address resolution is part of it BUT it resolves the address of the host doing the announcement not the host announced. Therefore, one needs to do another mDNS query resolving the .local hostnames. In Linux this is done by the system, however, it does not hurt to do it in the service discovery implementation.

DNS-SD also works over unicast. Avahi supports that type of lookup, this a very good fall back vehicle when you are in network with bad or no multicast support (e.g. RNDIS USB). Most implementations do not support that. This is huge advantage of JDNS or if you would create your own DNS-SD implementation (node-multicast-dns does only the multicast part, unicast lookup should be supported by node).

bobvanderlinden commented 8 years ago

Ah! That explanation makes things click. Ok, so we need to avoid mDNSResolver (dns_sd.h).

I get your initial idea now. A service that supports all of it, but is still language independent. Bindings still need to be written though. I like the idea, but I don't like running a separate non-standard service in the background. How about using the dbus API for Avahi? It does basically the same, but it is a standard service that is available on many systems. There are good bindings for dbus for most languages already. Python already has the Avahi API implemented on top of dbus, the same could be done for Node.

So, these are the options and their pro/cons:

  1. Use JDNS as a shared library
    • Requires some way to indicate where to find the JDNS shared library
    • Requries installing C/C++ tools on Windows
    • No Bonjour/Avahi needed
    • NodeJS-only
  2. Use JDNS compiled statically
    • Requires installing C/C++ tools on Windows
    • Requires to rework build system, as CMake isn't usually available for node modules
    • No Bonjour/Avahi needed
    • Cross-platform (Windows, Mac OSX and Linux)
    • NodeJS-only
  3. Use a JDNS precompiled library
    • Requires installing C/C++ tools on Windows (for Node bindings)
    • No Bonjour/Avahi needed
    • Cross-platform (Windows, Mac OSX and Linux)
    • NodeJS-only
  4. Use the Avahi API directly through dbus
    • Requires implementing Avahi API on Dbus
    • Requires Avahi to be installed and running (possibly annoying inside container)
    • No Windows or Mac OSX support
  5. Implement our own API and service based on Avahi
    • Language independent
    • Requires separate service to be running
    • No Windows or Mac OSX support (but can be changed later)
  6. Implement our own node module, possibly based on node-mdns-js and multicast-dns
    • Requires implementing quite a lot
    • Easy to install for users
    • No Bonjour/Avahi needed
    • Cross-platform (Windows, Mac OSX and Linux)
    • NodeJS-only
zhivko commented 8 years ago

@bobvanderlinden the result of avahi-browse-a:

+  wlan0 IPv6 Log service on beaglebone.local pid 7298      _machinekit._tcp     local
+  wlan0 IPv4 Log service on beaglebone.local pid 7298      _machinekit._tcp     local
+  wlan0 IPv6 beaglebone                                    Remote Disk Management local
+  wlan0 IPv6 klemenHp                                      Remote Disk Management local
+  wlan0 IPv4 beaglebone                                    Remote Disk Management local
+  wlan0 IPv4 klemenHp                                      Remote Disk Management local
+  wlan0 IPv6 beaglebone [54:4a:16:c5:d0:2a]                Workstation          local
+  wlan0 IPv6 klemenHp [00:22:fa:c5:c4:50]                  Workstation          local
+  wlan0 IPv4 beaglebone [54:4a:16:c5:d0:2a]                Workstation          local
+  wlan0 IPv4 klemenHp [00:22:fa:c5:c4:50]                  Workstation          local
+  wlan0 IPv4 647310736                                     _teamviewer._tcp     local

But machine box still empty. I guess It should show machinekit service on beaglebone?

When staring machinekit-example I get few errors like:

node index.js 
*** WARNING *** The program 'nodejs' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=nodejs>
*** WARNING *** The program 'nodejs' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=nodejs&f=DNSServiceRegister>
listening on http://localhost:3000/
Browser error { [Error: getaddrinfo -3008] code: -3008, errno: -3008, syscall: 'getaddrinfo' }
Browser error { [Error: getaddrinfo -3008] code: -3008, errno: -3008, syscall: 'getaddrinfo' }
machinekoder commented 8 years ago

@zhivko There is no Machinekit service shown at all. Have you started a Machinekit instance?

@bobvanderlinden If node will only run on Linux (which is likely, since it is the server) then the dbus avahi approach makes sense. There is actually an implementation for node based on dbus: https://github.com/izaakschroeder/node-avahi It is outdated an uses some weird 3rd party dbus interface, but I have also found dbus-native a native javascript dbus implementation. Combining those two could give us reasonable results with low effort.

zhivko commented 8 years ago

@strahlex machinekit is running on beaglebone. I have axis present and can jog steppers.

machinekoder commented 8 years ago

@bobvanderlinden Service discovery in Python uses exactly the same dbus interface. (https://github.com/strahlex/pymachinetalk/blob/master/pymachinetalk/dns_sd.py) However, the pythob-dbus implementation is not very good (uses gobject event loop). I am pretty sure node could handle this a lot better since it is made for multithreading.

machinekoder commented 8 years ago

@zhivko If you are running Axis you are not running Machinetalk. You need a (headless) Machinetalk capable configuration to try out node-machinetalk.

zhivko commented 8 years ago

OK I think I have enabled remote in linuxcnc.ini, avahi shows now:

kz@klemenHp:/$ avahi-browse -a
+  wlan0 IPv6 HAL Rcommand service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv6 HAL Rcomp service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv6 HAL Group service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv6 Log service on beaglebone.local pid 8447      _machinekit._tcp     local
+  wlan0 IPv4 Previewstatus service on beaglebone.local pid 8864 _machinekit._tcp     local
+  wlan0 IPv4 Preview service on beaglebone.local pid 8864  _machinekit._tcp     local
+  wlan0 IPv4 Command service on beaglebone.local pid 8864  _machinekit._tcp     local
+  wlan0 IPv4 Error service on beaglebone.local pid 8864    _machinekit._tcp     local
+  wlan0 IPv4 Status service on beaglebone.local pid 8864   _machinekit._tcp     local
+  wlan0 IPv4 File service on beaglebone.local pid 8864     _machinekit._tcp     local
+  wlan0 IPv4 HAL Rcommand service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv4 HAL Rcomp service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv4 HAL Group service on beaglebone.local pid 8472 _machinekit._tcp     local
+  wlan0 IPv4 Log service on beaglebone.local pid 8447      _machinekit._tcp     local
+  wlan0 IPv6 beaglebone                                    Remote Disk Management local
+  wlan0 IPv6 klemenHp                                      Remote Disk Management local
+  wlan0 IPv4 beaglebone                                    Remote Disk Management local
+  wlan0 IPv4 klemenHp                                      Remote Disk Management local
+  wlan0 IPv6 beaglebone [54:4a:16:c5:d0:2a]                Workstation          local
+  wlan0 IPv6 klemenHp [00:22:fa:c5:c4:50]                  Workstation          local
+  wlan0 IPv4 beaglebone [54:4a:16:c5:d0:2a]                Workstation          local
+  wlan0 IPv4 klemenHp [00:22:fa:c5:c4:50]                  Workstation          local

But browsing to http://localhost:3000/ still show empty machines box.

bobvanderlinden commented 8 years ago

Hmm, strange. Does the output of node tell you anything?