Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Java 16+ unix sockets #145

Closed
overheadhunter opened this issue Jul 29, 2021 · 33 comments
Closed

Use Java 16+ unix sockets #145

overheadhunter opened this issue Jul 29, 2021 · 33 comments

Comments

@overheadhunter
Copy link

I learned via a downstream bug report, that this library transitively depends on a several JNR libs. Which is fine unless certain configuration problems arise. 😉

When looking at the dependency tree I noticed, that the only reason why dbus-java requires those is for accessing unix sockets. Now that we have native support for unix sockets in Java 16+, I'd ask whether it is possible to replace jnr-unixsocket and get rid of a whole bunch of dependencies.

I know, this would increases the required Java version significantly. But as the current version is mature and stable, I would propose to schedule this for a major release that could live in parallel to the current version. No changes in the public API, so users can switch between versions depending on their needs.

If you think this would be feasible from a maintainers perspective, I would offer our help to implement this.

@hypfvieh
Copy link
Owner

I already read about that feature in newer Java versions.
I'm not sure that the internal implementation supports all features required by dbus-java (e.g. setting certain flags on the socket like SO_PASSCRED etc).

As this library still continues to support older versions of Java, using the native implementation is not possible.
Maintaining two different version is also nothing I'm planning to do (takes too much time and effort).

Increasing the required Java version was already on my todo list.
I also always prefer to use the Java LTS versions (1.8 is LTS, 11 is LTS and 17 will be LTS).
The next Java version required by dbus-java would be 11, so any other project can still use this library without requiring a bleeding edge Java version.

One thing I could think of is to change the unix socket parts to use service loader to switch the "unix socket backend".
This is something I already do to support file descriptor passing using a third party library.
I'll have to investigate that as well as the supported socket options (like said before).

@overheadhunter
Copy link
Author

I'm not sure that the internal implementation supports all features required by dbus-java (e.g. setting certain flags on the socket like SO_PASSCRED etc).

Ok that needs to be checked, of course.

Maintaining two different version is also nothing I'm planning to do (takes too much time and effort).

That's what I assumed. But I still had a little hope that (judging from the low number of open issues), the current version is mature and doesn't require that much attention, so focus can shift to a new version. 😉

One thing I could think of is to change the unix socket parts to use service loader to switch the "unix socket backend".
This is something I already do to support file descriptor passing using a third party library.
I'll have to investigate that as well as the supported socket options (like said before).

While certainly good software design, this might not be that much less effort, since it would require at least two, maybe three new libraries:

  1. The API definition of said service (and possibly the JEP 380 implementation as a MR-JAR)
  2. The JNR implementation providing the service
  3. (if not already part of 1., the JEP 380-based service provider)

@hypfvieh
Copy link
Owner

hypfvieh commented Jul 30, 2021

That's what I assumed. But I still had a little hope that (judging from the low number of open issues)

Low number of issues does not mean there are no bugs or that there is no room for improvement.
There are parts which could certainly need some more attention (rewriting), but I'm pretty busy lately...

As far as I can see in the description of JEP 380, it seems that it does not support all feature required by dbus-java:

Non-Goals

It is not a goal to support features that are not common across the major Unix platforms and Windows. This includes Linux-specific features such as the abstract filesystem-independent namespace. It also includes features that are generally supported on Unix but unsupported on Windows, such as socket pairs. Support for these features can be revisited in the future if needed and, in the case of the missing Windows features, if the Windows platform evolves to support them. An exception to this non-goal is support for peer credentials, which can be implemented as a JDK specific socket option on platforms that support it. Other socket options may be investigated as follow up work, possibly also as JDK specific options, after this JEP is completed.

One issue would be: dbus-java supports abstract unix sockets, which is not part of JEP 380 because windows does not support that.
But I will take a look at that if I find some spare time to do it.

@brett-smith
Copy link
Contributor

I have some interest in this. I am using the embedded bus on Windows, and so have to use the TCP transport on this OS. It is noticeably slower communication between the client and server when compared to running on OS X or Linux both using domain sockets. So frankly any alternative on Windows would be great.

I had actually started on implementation on Windows that used name pipes, but while this whould be relatively easy with JNA (there is code already written), I couldn't find enough documentation on JNR to fit in with dbus-java and lost interest!

I can also confirm that you don't have to use an abstract path on Linux, you can connect to the system or session dbus fine or create your own embedded bus using a real path. OS X doesn't support abstract paths either.

@hypfvieh
Copy link
Owner

When just using the library as "client", abstract sockets are not really needed. But when using the DBusDaemon class, abstract might be required.
In the current version, this is not handled at all. If you use the DBusDaemon on OS X with a bus address indicating an abstract unixsocket, it will probably throw an exception.

I'll have to dig into this when I have some spare time left.
Handling the abstract socket stuff should be improved.
Switching the 'transport' of DBus is already supported (e.g. using TCP instead of UnixSocket), so maybe I can improve that part to allow using another implementation of unixsocket... I'll have to check that.

Also I still want to support older Java versions (especially 11) when 17 will be released next month.
I'm also a fan of using newer Java versions, but when providing libraries always forcing the users to use bleeding edge Java is not a good idea. In case someone uses this library on some IOT devices, it might be difficult to update/switch to a recent Java version.

I few years ago when I started with dbus-java there were still frameworks for IOT forcing the developer to provide their plugins with Java 1.7 compatibility. That was really annoying, so my policy is to at least support the previous Java LTS version.
In this case this would mean I would bump the required Java version to 11 shortly after 17 is released.

@brett-smith
Copy link
Contributor

brett-smith commented Aug 31, 2021

OS X does indeed throw an exception if you try to use an abstract, but I am successfully using DBusDaemon (and a client) with domain sockets with a non-abstract path so am not too bothered by this.

As another vote for having Java 16 domain socket support ... I have a research project scheduled to see if the application I use dbus-java with can be compiled with GraalVM native-image. One of the sticking points is almost certainly going to be dbus-java itself because of it's use of JNR.

The last time I looked, neither JNA or JNR were fully supported. Graal SDK does have it's own FFI, but that would mean a rewrite of jnr-unixsocket. However, Graal do have an experimental Java 16 build that presumably can access this built-in domain socket support. So if that is possible, then that would give you Graal support for free

@hypfvieh
Copy link
Owner

hypfvieh commented Sep 2, 2021

I just started to take a closer look to native unixsockets in Java 16.
There are some more 'special handlings' beside the 'abstract' unix socket which is not required.

Especially the authentication stuff (SO_PASSCRED vs SO_PEERCRED) and the handling of all of this on BSD and OS X.
For BSD / OS X, there is some special treatment for the credential part relying heavily on jnr-posix (see FreeBSDHelper).
I removed / commented the code in SASL / NativeUnixTransport which calls FreeBSDHelper. I don't know if all of this is required when using native unixsockets and I don't have FreeBSD or OS X to test that.

More changes were required due to the fact that the Java 16 unix sockets uses NIO while the current implementation uses the old socket API.
All methods which required Socket / InputStream / OutputStream were changed to support SocketChannel, including the SASL stuff.
This will also breaks the SPI/ServiceLoader stuff used to support dbus-java-nativefd.

My first tests using TestAll/TestLowLevel on an Ubuntu Linux machine worked.
More tests on other OSes would be appreciated.

I'm not sure about including or not including jnr-unixsocket as dependency.
At the moment I have a few ideas:

  1. use ServiceLoader (or something like that) and provide the backend separately (e.g. dbus-java-jnr-unixsockets / dbus-java-native-unixsockets), the developer has to choose one of them
  2. use ServiceLoader, include dbus-java-jnr-unixsockets by default, so developer can exclude this and include dbus-java-native-unixsockets if needed
  3. Provide both implementations in dbus-java, using native version if suitable JRE (16+) is found, use jnr otherwise
  4. Provide both implementations, use jnr per default and native only when certain property (or similar) is set

I uploaded the current development state to a new branch (java16-unixsocket) which is work-in-progress.
To use the new unixsocket implementation, you need Java 16 (or higher).
If you run it from your development IDE, you can force it to use the new implementation by setting the system property dbus.java.disable.jnr-unixsocket to true (look at the TestAll/TestLowLevel).
If you run it in your application you may exclude jnr-unixsocket from the dependencies (not tested yet).

@brett-smith
Copy link
Contributor

Thanks for making a start on this. Ive given it a try on Linux, Windows and Mac. The only real success so far I've had is connecting to the system bus on Linux. As the primary purpose of dbus-java, this works as well as it ever did.

However, I can't seem to get EmbeddedDBusDaemon working at all with either TCP or domain sockets on any operating system.

There were a few tweaks I needed to get it to compile in my environment. module-info.java needed updating. All the JNR modules needed to be made static (optional), and 3 new JDK modules needed to be added - java.security.sasl, jdk.security.auth and jdk.net.

It seemed I needed to add listen=true to the address set on the embedded daemon. That got me a little further. Maybe this was always required, but I didn't used to have to do this on stable dbus-java.

On Windows, I noticed the path is stil /tmp/dbus-XXXXX, does this need to be a window path? If so the system property java.io.tmpdir may be a way to contstruct that path in a cross platform way.

The main problem now seems to be starting the embedded daemon, then getting a connection to export some services on (in the same JVM). This results in two exceptions, one on the daemon thread and on the main thread where I am trying to connect to the daemon.


java.lang.RuntimeException: java.net.BindException: Address already in use
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startInForeground(EmbeddedDBusDaemon.java:58)
	at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.net.BindException: Address already in use
	at java.base/sun.nio.ch.UnixDomainSockets.bind0(Native Method)
	at java.base/sun.nio.ch.UnixDomainSockets.bind(UnixDomainSockets.java:111)
	at java.base/sun.nio.ch.ServerSocketChannelImpl.unixBind(ServerSocketChannelImpl.java:319)
	at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:292)
	at java.base/java.nio.channels.ServerSocketChannel.bind(ServerSocketChannel.java:223)
	at org.freedesktop.dbus.connections.transports.NativeUnixSocketTransport.connectImpl(NativeUnixSocketTransport.java:62)
	at org.freedesktop.dbus.connections.transports.AbstractTransport.connect(AbstractTransport.java:109)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startSocket(EmbeddedDBusDaemon.java:84)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.listen(EmbeddedDBusDaemon.java:73)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startInForeground(EmbeddedDBusDaemon.java:55)
	... 1 more
05-09-2021 00:05:20 [DBusConnection] ERROR IncomingMessageThread - FatalException in connection thread.
org.freedesktop.dbus.exceptions.FatalDBusException: Underlying transport returned EOF (1)
	at org.freedesktop.dbus.connections.AbstractConnection.readIncoming(AbstractConnection.java:1149)
	at org.freedesktop.dbus.connections.IncomingMessageThread.run(IncomingMessageThread.java:39)
05-09-2021 00:05:20 [DBus Sender Thread-1] INFO  DBusConnection - Setting reply to MethodCall(0,1) { Path=>/org/freedesktop/DBus, Interface=>org.freedesktop.DBus, Member=>Hello, Destination=>org.freedesktop.DBus } { } as an error

Regarding your points as to how this could be plugged in, my preference would be for ServiceLoader to be used. This fits in with JPMS and opens the possibility of adding further novel transports as add-ons. There would be nothing stopping you using ServiceLoader internally in dbus-java to discover all supported / available transports.

@hypfvieh
Copy link
Owner

hypfvieh commented Sep 5, 2021

Thanks for the feedback. I have to admit I didn't look at modules-info (and I still don't really understand JPMS).
I added your suggestions regarding it in my branch.

EmbeddedDbusDaemon is broken due to my refactoring. I will fix it and implement the ServiceLoader stuff as well.

The thing with the '/tmp' path is simply wrong, and is also wrong in upstream. Instead of using java.io.tmpdir, "/tmp" is used hardcoded. I will fix this as well and post an update here when you can pull the branch again.

@hypfvieh
Copy link
Owner

hypfvieh commented Sep 7, 2021

Alright, it has taken some more time than I excepted, but I just pushed my latest changes.

The library has been splitted into multiple parts:

  • dbus-java (core library)
  • dbus-java-transport-jnr-unixsocket (jnr-unixsocket implementation)
  • dbus-java-transport-native-unixsocket (Java 16+ unix socket implementation)
  • dbus-java-transport-tcp (TCP implementation)
  • dbus-java-tests (containing all unit test to allow running them against each transport)

The system property stuff used before has been removed.

So the big change is that you have to add at least on of the transport modules as well as dbus-java itself. Adding both, jnr and native unix socket will cause dbus-java to throw an exception because there can only be one protocol provider at the same time. In this case both provide support for 'unix' protocol, so you have to choose one of them (otherwise it would be more or less random which gets used during runtime). Combining jnr or native with tcp is still possible.

Maybe I'll rename dbus-java to dbus-java-core to make clear that it does not work without any of the transport implementations.

All parts now contain a module-info.java file and will also require at least Java 11 (Java 16+ is required when using native transport).

During my refactoring I saw that there are not so many tests running with the TCP version. Most tests use unix socket or fail due to authentication issues. E.g. when running TestPeer2Peer using TCP on both ends (EmbeddedDbusDaemon + DBusConnection), authentication always fails because SASL switches from AUTHENTICATED state to FAILED state.. I'm not sure whats wrong at that point, but as those tests where never executed using TCP, I just disabled them for TCP right now.
Maybe someone has some idea what's wrong here.

All unit tests are working with native/jnr when using linux. No other OSes tested yet.
I also hope I fixed the broken DBusDaemon ;)

Feedback welcome!

@brett-smith
Copy link
Contributor

brett-smith commented Sep 7, 2021

Thanks, its looking a lot more polished already, but I am still seeing more or less the same thing as I saw before though, which sounds like the same case as TestPeer2Peer.

I have an app that is in two parts. The service part exports a DBus interface on the system or session bus if it's there, but it tries to start an embedded bus if one doesn't exist (i.e. usually Mac and Windows). It then tries to export the service on this embedded bus. So it's all happening in the same JVM.

FWIW, the same now seems to happen with both JNR and native domain sockets so hopefully it's just an effect of the refactoring rather than a limitation of native sockets.

I'll post if I can find why.

Edit: Found one thing. TestPeer2Peer can be made to work, if you change TcpTransport SASL auth mode to AUTH_ANON.

    TcpTransport(BusAddress _address, int _timeout) {
        super(_address);
        timeout = _timeout;
//        setSaslAuthMode(SASL.AUTH_SHA);
        setSaslAuthMode(SASL.AUTH_ANON);
    }

Edit 2: While that makes TestPeer2Peer complete, it doesn't completely help in my application. It gets further, but then hangs trying to obtain a connection to the embedded bus (again using TCP).

@hypfvieh
Copy link
Owner

hypfvieh commented Sep 8, 2021

Using SASL.AUTH_ANON is not a proper solution as the DBus specifications says that you have to use the SHA method when connecting to DBus using TCP. And because it is possible to run the original dbus daemon in TCP mode, dbus-java should be able to connect to it.

I did some more investigation and it seems that SASL is somehow broken when using TCP. If you use the current master branch and change the CONNECTION_ADDRESS in TestPeer2Peer to a TCP based address, the test will always fail.
For me it looks like the SASL response send in doResponse when using AUTH_SHA should be OK not CONTINUE.
When changing the return value to OK TestPeer2Peer passes on master and passes on the branch (please pull branch, master is still unmodified).

Some of the other tests still fail or get stuck (e.g. TestTwoPart). Maybe I'll take a look at that later.

@overheadhunter
Copy link
Author

Feedback welcome!

Any chance you could publish this as an alpha version? This would simplify creating adjusted test versions of downstream projects.

@purejava @swiesend What do you think? Would this change help to modularize and reduce dependencies of kdewallet/secret-service libs?

@purejava
Copy link

@purejava @swiesend What do you think? Would this change help to modularize and reduce dependencies of kdewallet/secret-service libs?

Besides depending on dbus-java, kdewallet has only JUnit and SLF4J as further dependencies. So, on modularizing kdewallet, the dependencies for one part/module would be significantly reduced.

@purejava
Copy link

I add this change to my to do list.

@hypfvieh
Copy link
Owner

Any chance you could publish this as an alpha version? This would simplify creating adjusted test versions of downstream projects.

No plans to release any sort of unfinished version (alpha, beta ,whatever you wanna call it).
You can build the current version yourself by cloning the source and switch to branch java16-unixsocket.
To speed up building, disabled tests (mvn clean install -DskipTests=true).

I'm still investigating the issues when using TCP transport. I don't think it is a show stopper, as the failing/blocking tests were never designed or ran with TCP as transport type.
All of the tests mentioned in my earlier post are designed to run using unix socket and locally running DBus daemon.
I want to change that and run the tests with all transports using EmbeddedDBusDaemon when no local TCP DBus daemon is available.

@hypfvieh
Copy link
Owner

Alright, I just pushed my latest changes.
All tests can now be run with all transport types (JNR + Native Unixsockets / TCP).
Running all tests with all transports takes some time (around 3 to 4 minutes on my development machine).

During developing I found some more issues introduced by some recent PRs. I fixed them in branch and upstream as well.

@brett-smith
Copy link
Contributor

Thanks for the update. The EmbeddedDaemon is now working great again with TCP, and the system/session bus is still perfect, but I am still having exactly the same issue when trying to use domain sockets with EmbeddedDaemon, either JNR or native. java.io.IOException: Address already in use.

14-09-2021 22:18:42 [main] INFO  Main - Using UNIX domain socket bus
14-09-2021 22:18:42 [main] INFO  Main - Starting embedded bus on address unix:abstract=/tmp/dbus-CTZQNUDFDO,guid=1ef2c17cba8a2cbf8343b9ba49740107,listen=true (auth types: ANON)
14-09-2021 22:18:42 [main] INFO  Main - Started embedded bus on address unix:abstract=/tmp/dbus-CTZQNUDFDO,guid=1ef2c17cba8a2cbf8343b9ba49740107,listen=true
14-09-2021 22:18:42 [main] INFO  Main - Connecting to embedded DBus unix:abstract=/tmp/dbus-CTZQNUDFDO,guid=1ef2c17cba8a2cbf8343b9ba49740107
14-09-2021 22:18:43 [EmbeddedDBusDaemon-unix: {guid=1ef2c17cba8a2cbf8343b9ba49740107, abstract=/tmp/dbus-CTZQNUDFDO, listen=true}] ERROR EmbeddedDBusDaemon - Got uncaught exception
java.lang.RuntimeException: java.io.IOException: Address already in use
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startInForeground(EmbeddedDBusDaemon.java:63)
	at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.io.IOException: Address already in use
	at jnr.unixsocket.Common.bind(Common.java:66)
	at jnr.unixsocket.UnixServerSocket.bind(UnixServerSocket.java:53)
	at jnr.unixsocket.UnixServerSocket.bind(UnixServerSocket.java:46)
	at org.freedesktop.dbus.transport.jnr.UnixSocketTransport.connectImpl(UnixSocketTransport.java:57)
	at org.freedesktop.dbus.connections.transports.AbstractTransport.connect(AbstractTransport.java:117)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startSocket(EmbeddedDBusDaemon.java:93)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.listen(EmbeddedDBusDaemon.java:82)
	at org.freedesktop.dbus.bin.EmbeddedDBusDaemon.startInForeground(EmbeddedDBusDaemon.java:60)
	... 1 more
14-09-2021 22:18:43 [DBusConnection] ERROR IncomingMessageThread - FatalException in connection thread.
org.freedesktop.dbus.exceptions.FatalDBusException: Underlying transport returned EOF (1)
	at org.freedesktop.dbus.connections.AbstractConnection.readIncoming(AbstractConnection.java:1154)
	at org.freedesktop.dbus.connections.IncomingMessageThread.run(IncomingMessageThread.java:39)
14-09-2021 22:18:43 [DBus Sender Thread-1] INFO  DBusConnection - Setting reply to MethodCall(0,1) { Path=>/org/freedesktop/DBus, Interface=>org.freedesktop.DBus, Member=>Hello, Destination=>org.freedesktop.DBus } { } as an error

At this stage, I can only guess I am doing something in the setup of the embedded daemon. Here is a fragment of my code that deals with this (worked with 3.x branch fine).

boolean startedBus = false;
if(daemon == null) {
	BusAddress listenBusAddress = new BusAddress(newAddress);
	String listenAddress = newAddress;
	if(!listenBusAddress.isListeningSocket()) {
		listenAddress = newAddress + ",listen=true";
		listenBusAddress = new BusAddress(listenAddress);
	}

	log.info(String.format("Starting embedded bus on address %s (auth types: %s)", listenBusAddress.getRawAddress(), toAuthTypesString(authTypes)));
	daemon = new EmbeddedDBusDaemon(listenBusAddress);
	daemon.startInBackground();
	log.info(String.format("Started embedded bus on address %s", listenBusAddress.getRawAddress()));
	startedBus = true;
}


/* Because we cannot guarantee that the daemon is truly started and listening
 * for connections at this point (startInBackground() was used), try multiple
 * times. 
 */
log.info(String.format("Connecting to embedded DBus %s", busAddress.getRawAddress()));
for (int i = 0; i < 6; i++) {
	try {
		conn = DBusConnection.getConnection(busAddress.getRawAddress());
		log.info(String.format("Connected to embedded DBus %s", busAddress.getRawAddress()));
		break;
	} catch (DBusException dbe) {
		if (i > 4)
			throw dbe;
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			throw new IOException("Interrupted. ", e);
		}
	}
}

Have you got any more pointers on what this might be? I will try to get an isolated unit test to demonstrate, will post back here.

On a testing side note. I tried running the test suite, but it fails from command line mvn with what looks like some JPMS related errors. I can run the suite through Eclipse's maven though, and that appears to complete. Running the units tests directly in Eclipse fails out-of-the-box because all providers are on the test classpath, and it doesn't understand executions.

I can make the direct Eclipse tests work by separating the 3 dependencies into 3 Maven profiles and selecting the one I want to use. It should be possible to keep the current behaviour of running all tests for all providers via Maven, but make this selectable by turning off profiles. Ill make a PR when this dust has settled on version 4.0.0.

@hypfvieh
Copy link
Owner

Thanks for your feedback. I could reproduce your issue and fixed in the branch (please pull).
Out of curiosity: Why are you using String.format in your calls to the logger?
String replacement is usually handled by the logger (using the '{}' placeholder), so concatenation is done lazily (and not done at all if message would not be logged because of disabled log level).

@brett-smith
Copy link
Contributor

brett-smith commented Sep 15, 2021

That's got it! Everything is working exactly as it was with 3.x now. I will be trying this out on Windows and OS X tomorrow. If I get some time I might throw together a performance test.

I also took your polling code from EmbeddedDaemonTest and used that instead of trying to make a connection. It makes me wonder if startInBackground() (or a new method) should be a bit smarter and block itself until the first socket is waiting, either using similar polling code or a Semaphore or some other locking mechanism. I would guess many use cases of EmbeddedDaemon would be starting and immediately making a connection to export something on it, so everyone is going to need that polling.

Am particularly looking forward to seeing domain sockets on Windows. I picked dbus-java for a cross platform app that needed an RPC mechanism because I don't particularly like RMI, and found other frameworks too heavyweight. Having used DBus on Linux for native apps, it seemed like a good idea. This change makes it an even more viable answer for people who might want cross platform RPC. DBus has it short comings of course, but for many purposes its great.

Regarding String.format() .. yeh ... just bad habit stemming from working with loggers that didn't have this and company code styles (for right or wrong). I know there is really no excuse these days, it will get changed eventually :)

Edit: @overheadhunter asked about builds. Anybody who might want to try this out is welcome to use my employers public Maven repository. It has builds of 4.0.0-SNAPSHOT that are up to date as of now. The repository is available long term, but I can't guarantee when updates might be uploaded. I tend to use dbus-java often so it should be fairly often.

<repository>
	<id>jdaptive-ext-snapshots</id>
	<name>artifactory.javassh.com-snapshots</name>
	<url>https://artifactory.jadaptive.com/ext-snapshots-local</url>
	<snapshots/>
        <releases>
            <enabled>false</enabled>
         </releases>
</repository>

@brett-smith
Copy link
Contributor

brett-smith commented Sep 16, 2021

Have some feedback on OS X. It does all work, but there are is a change in behaviour that is causing me problems. My app is in two parts. The service runs as root. It starts EmbeddedDBusDaemon and exports a service. The client is an ordinary user, and connects to the daemon. I can no longer connect the client to service, unless I run the client as root as well.

It turns out previously, I was using EmbeddedDBusDaemon.setAuthMode(SASL.ANON) which is no longer available. Obviously this is not ideal security wise, but the pipe was protected enough by file permissions for my purposes.

This works ok on Linux. My deployed app uses the system bus and a policy file to open up the exported interface.

In the absence of having policy files with the embedded daemon, and now no setAuthMode(), how would I connect DBus between different users?

I noticed that the UID is retrieved using com.sun.security.auth.module.UnixUser(), and for windows this just returns zero so I suspect this might work on Windows, or possibly break in another way :) You also might want to add that to module-info.java. My app uses a cut down JVM and I had to add jdk.security.auth module for this class to load on OS X.

@hypfvieh
Copy link
Owner

I updated EmbeddedDbusDaemon to support setting the auth mode again.
Also the usage of TransportFactory is now deprecated. If you use it somewhere, replace it with the new TransportBuilder.

I don't understand your issue with jdk.security.auth as this is already defined in module-info.class of dbus-java core module and is only used in SASL.java inside of that module.

@brett-smith
Copy link
Contributor

brett-smith commented Sep 17, 2021

Fantastic, thanks. I'll give that a try now.

I was indeed using TransportFactory, will change.

And apologies, I missed that jdk.security.auth was already in module-info. It was just my cut down JVM that was missing it.

@brett-smith
Copy link
Contributor

That all works good. Now Linux, OS X and Windows are all behaving in the same way as they were with the 3.x.x version, i.e. unix and linux can use domain sockets and windows can use TCP sockets.

However, I seem to have hit a snag on Windows at the very last minute when using native sockets. My service successfully starts an Embedded Daemon, connects to that and exports a service on it. I can see a file in the file system.

If I then try to connect a client to this, either as an administrator or normal user, the connections appears to just hang. It's like only a single connection is being accepted on the server end of the socket. Hopefully this isnt a limitation of either the JDK, or Windows domain socket support.

@hypfvieh
Copy link
Owner

Can you create a sample for reproducing this behavor?
I tried to reproduce it on my windows machine without any success (using latest JDK 16, Windows 10 20H2, 19042.1165).

I created 2 classes (RunTwoPartServer / RunTwoPartClient).
The server running in the first JVM exporting an interface with one method which will return a String ("Hello" in this case).
The second class is the client which will be ran in another JVM, fetching the exported object and calling the method of it every 500ms.

My client side looks like this:

try (DBusConnection conn = DBusConnection.getConnection(twopartAddress)) {
	// repeat until JVM is killed
	while (true) {
		IExport remoteObject = conn.getRemoteObject(RunTwoPartDaemon.EXPORT_NAME, "/", IExport.class);
		System.out.println("Remote side says: " + remoteObject.sayHello());

		try {
			Thread.sleep(500L);
		} catch (InterruptedException ex) {
		}
	}

} catch (IOException | DBusException e) {
	throw new RuntimeException("Could not connect to twopart daemon");
}

I can connect multiple clients using native unixsocket without any blocking. All clients will print 'Remote side says: Hello'.

I added my sample code to my sandbox repository, maybe you take a look at it.

@brett-smith
Copy link
Contributor

You are absolutely right, multiple connections do work. It turns out this problem only shows itself on the installed version of my app. Running the two parts as the same user from an IDE (Eclipse) using a domain socket is absolutely fine.

I suspect it's something permissions related on the socket file. The component that embeds the dbus daemon runs on Windows as a service, and as the "Local System" account. This is not the same as "Administrator" apparently.

The one thing that makes me doubt this is there are no exceptions, it just hangs. Will post back when I know some more.

@brett-smith
Copy link
Contributor

Confirmed as permissions. Adding this to the setup of the embedded daemon does the trick.

/* WARN: Very loose permissions, anyone can access the bus */  
if (busAddress.getBusType().equals("UNIX")) {
	Path path = Paths.get(busAddress.getPath());
	if(Util.isWindows()) {
	    AclFileAttributeView aclAttr = Files.getFileAttributeView(path, AclFileAttributeView.class);
	    UserPrincipalLookupService upls = path.getFileSystem().getUserPrincipalLookupService();
	    UserPrincipal user = upls.lookupPrincipalByName("Everyone") /* TODO Tighten this */;
	    AclEntry.Builder builder = AclEntry.newBuilder();       
	    builder.setPermissions( EnumSet.of(AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_DATA));
	    builder.setPrincipal(user);
	    builder.setType(AclEntryType.ALLOW);
		aclAttr.setAcl(Collections.singletonList(builder.build()));
	}
	else { 					
		Files.setPosixFilePermissions(path, 
				new LinkedHashSet<>(Arrays.asList(PosixFilePermission.values())));
	}
}

@brett-smith
Copy link
Contributor

brett-smith commented Sep 26, 2021

I thought I'd post the results of a very quick and dirty performance test too (on Windows). Using your RunTwoPart* classes as a basis, I simply called sayHello() 10000 times and timed the results. This had to be done at nanosecond resolution to get meaningful results.

TCP: 10000 calls took 90673495300ns, 9067349ns per call

and

UNIX: 10000 calls took 5793327700ns, 579332ns per call

So that's more than 15 times quicker! I'm very happy with this, it's exactly the original reason I took an interest in this ticket. I'm sure sure the OP will be very happy with the cut down dependencies. Thanks for all your time on this change. dbus-java is better for it.

Edit: Forgot to mention, I noticed a minor mistake in RunTwoPartDaemon.main(). It is testing _args[1] where it should be _args[0].

@hypfvieh
Copy link
Owner

Thanks for your feedback. Sounds great that using unix socket will speed up everything on windows as well.

I added some changes to allow setting the file owner, group and unix file permissions (obviously only on Unix/Linux systems) of the socket file created by TransportBuilder. The required methods are also exposed on EmbeddedDBusDaemon.
Maybe you can take a look if this suits your needs.

btw. I also fixed the usage of the wrong args index in my sandbox sample, thanks for the hit ;)

@AsamK
Copy link
Contributor

AsamK commented Dec 5, 2021

I've been using the dbus-java development version with java 16 unix sockets for some time now and haven't noticed any issues.
Are there plans when to release this as a stable version?

@hypfvieh
Copy link
Owner

hypfvieh commented Dec 5, 2021

No schedule yet. Maybe end of the month or early next year.

@hypfvieh
Copy link
Owner

Version 4.0.0 has been released and should be available in maven central soon.

@brett-smith
Copy link
Contributor

dbus-java is now dbus-java-core

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants