gluonhq / substrate

Create native Java(FX) apps for desktop, mobile and embedded
GNU General Public License v2.0
390 stars 52 forks source link

Impossible to download anything (No route to host) from IPv6 domains on devices supporting IPv6 #1224

Open salmonb opened 1 year ago

salmonb commented 1 year ago

On devices supporting IPv6, it's impossible to download anything from domains having a IPv6 DNS record.

For example, an ImageView with a URL pointing to such domain will show no image. No error is raised or logged in this case.

A call to URL.openStream() on such URL throws a "No route to host" exception. This issue happens only in the Gluon app, not in other apps of the device (pinging that domain or browsing that same URL in the browser works fine for example).

I reproduced the issue on iOS and iPadOS, not on Android but maybe just because my Android device is too old and doesn't support IPv6.

Expected Behavior

ImageView and URL.openStream() should work with IPv6 domains (as they do with IPv4 domains).

Current Behavior

ImageView and URL.openStream() don't work with IPv6 domains.

Steps to Reproduce

Here is a reproducer:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import java.net.InetAddress;
import java.net.URL;
import java.util.stream.IntStream;

public class GluonIpv6ImageIssueApplication extends Application {

    private static final String[] IMAGE_URLS = {
            // These first 2 domains are ipv4 only, and so these images are loaded correctly
            "https://images.freeimages.com/images/large-previews/44c/blue-and-yellow-macaw-1641749.jpg",
            "https://thumbor.forbes.com/thumbor/fit-in/1290x/https://www.forbes.com/advisor/wp-content/uploads/2022/03/build_a_website_for_free_-_article_image.jpg",
            // These last 2 domains are ipv6, and so these images are not loaded
            "https://cdn.pixabay.com/photo/2023/06/18/18/05/rose-8072535_1280.jpg",
            "https://img.freepik.com/free-photo/neon-tropical-monstera-leaf-banner_53876-138943.jpg?w=2000&t=st=1690189691~exp=1690190291~hmac=3dabe10bddd200b7ce1cb2f881174a9ecd8b2fd59b89491972b235af3d3777e0"
    };

    private final BorderPane root = new BorderPane();

    @Override
    public void start(Stage primaryStage) {
        FlowPane top = new FlowPane(IntStream.range(0, IMAGE_URLS.length).mapToObj(this::createThumbnail).toArray(Node[]::new));
        top.setAlignment(Pos.CENTER);
        BorderPane.setMargin(top, new Insets(10));
        root.setTop(top);
        root.setBackground(Background.fill(Color.BLACK));
        primaryStage.setScene(new Scene(root, 800, 600));
        primaryStage.show();
    }

    private Node createThumbnail(int i) {
        Text imageNumber = new Text(String.valueOf(i + 1));
        imageNumber.setFill(Color.WHITE);
        String imageUrl = IMAGE_URLS[i];
        Text host = new Text();
        host.setFill(Color.WHITE);
        Text ipAddress = new Text();
        ipAddress.setFill(Color.WHITE);
        try {
            URL url = new URL(imageUrl);
            host.setText(url.getHost());
            ipAddress.setText(InetAddress.getByName(url.getHost()).getHostAddress());
        } catch (Exception e) {
            if (host.getText() == null)
                host.setText("???");
            if (ipAddress.getText() == null)
                ipAddress.setText("???");
        }
        VBox thumbnail = new VBox(imageNumber, host, ipAddress);
        thumbnail.setAlignment(Pos.CENTER);
        thumbnail.setPadding(new Insets(5));
        thumbnail.setPrefSize(50, 50);
        thumbnail.setBackground(Background.fill(Color.BLUEVIOLET));
        thumbnail.setBorder(Border.stroke(Color.WHITE));
        thumbnail.setOnMouseClicked(e -> root.setCenter(createImageView(imageUrl)));
        thumbnail.setCursor(Cursor.HAND);
        FlowPane.setMargin(thumbnail, new Insets(5));
        return thumbnail;
    }

    private ImageView createImageView(String imageUrl) {
        ImageView imageView = new ImageView();
        Image image = new Image(imageUrl, true);
        imageView.setImage(image);
        imageView.fitWidthProperty().bind(root.widthProperty());
        imageView.setPreserveRatio(true);
        return imageView;
    }

}

The application shows 4 thumbnails showing the image number, the hostname it comes from, and its IP address. The first 2 domains are IPv4 domains only, but the last 2 are IPv6 domains, as you can see below when running this app on a device supporting IPv6:

IMG_165B9C454F48-1

When clicking on a IPv4 thumbnail, the image is loading correctly (as shown above). But when clicking on a IPv6 thumbnail, no image is loaded (as shown below).

IMG_B92C5188ECC9-1

Your Environment

Issue reproduced on iPad mini 6 and iPhone SE 2017

johanvos commented 1 year ago

Good isse. I can imagine this is happening because the IPv6 classes are not added to the reflection config files.

tiainen commented 1 year ago

javafx.scene.image.Image has an error and exception property which get filled in when something unexpected happened while loading the image. You can add a listener and print the stack trace to see more info about the reason of the loading failure.

image.exceptionProperty().addListener((obs, ov, nv) -> {
    if (nv != null) nv.printStackTrace();
});
salmonb commented 1 year ago

@johanvos Not sure IPv6 are well separated classes in the JDK. FYI here is the trace I'm getting when trying URL.openStream():

[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB] java.net.NoRouteToHostException: No route to host
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VA_LIST_NoRouteToHostException_constructor_96b3f65891b472d5be3aacfd448298970f4e1ca0(JNIJavaCallWrappers.java:0)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.nio.ch.Net.connect0(Net.java)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.nio.ch.Net.connect(Net.java:579)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.nio.ch.Net.connect(Net.java:568)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:588)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at java.net.Socket.connect(Socket.java:633)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.security.ssl.BaseSSLSocketImpl.connect(BaseSSLSocketImpl.java:174)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.NetworkClient.doConnect(NetworkClient.java:183)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.http.HttpClient.openServer(HttpClient.java:498)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.http.HttpClient.openServer(HttpClient.java:603)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:266)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:189)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1242)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1128)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:175)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1665)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224)
[Mon Jul 24 13:04:35 BST 2023][INFO] [SUB]      at java.net.URL.openStream(URL.java:1161)

Here is the last Java code before JNI at Net.java:579

    static int connect(ProtocolFamily family, FileDescriptor fd, InetAddress remote, int remotePort)
        throws IOException
    {
        if (remote.isLinkLocalAddress()) {
            remote = IPAddressUtil.toScopedAddress(remote);
        }
        boolean preferIPv6 = isIPv6Available() &&
            (family != StandardProtocolFamily.INET);
        return connect0(preferIPv6, fd, remote, remotePort); // <= failing JNI call
    }

Thanks @tiainen for the exceptionProperty, I forgot about that...

dave-braithwaite-fa commented 2 months ago

We're seeing this issue too. Has there been progress on this?

salmonb commented 2 months ago

I would love to have this issue resolved too, but I guess this is happening at the low level API, so in GraalVM rather than in Gluon Substrate itself.

The current version of Gluon Substrate is designed to work with its own fork of GraalVM, based on version 22.1.0.1 (2 years old). Oracle released newer versions of GraalVM since that time, and this issue may be solved now in GraalVM.

So I think the question for this issue is: does Gluon have a plan to update its fork to a newer GraalVM version?