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

WASI VirtualSocket interface and Java networking classes implementation #998

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

mandesero
Copy link

This pull request introduces two key updates to enhance networking capabilities in a WASI-based environment:

Patch 1: WASI: VirtualSocket Interface

This patch provides the core functionality required for socket operations within the WASI environment. Key features include:

  • Creating, binding, connecting, and listening to sockets.
  • Sending and receiving data.
  • Configuring socket options such as broadcast and reuse.
  • Retrieving peer and socket names.

⚠️ This interface is specific to WASI-based WebAssembly and is not supported for JavaScript or non-WASI WebAssembly environments.

Patch 2: classlib: Implement java.net.Socket

This patch introduces the implementation of the java.net.Socket class, enabling developers to work with sockets in a WASI-based environment. Additionally, the following related networking classes are implemented:

  • java.net.ServerSocket
  • java.net.InetAddress
  • java.net.Inet4Address
  • java.net.Inet6Address
  • java.net.SocketAddress
  • java.net.InetSocketAddress
  • java.net.Proxy
  • java.net.NetworkInterface
  • java.net.SocketException
  • java.net.UnknownHostException

@konsoletyper
Copy link
Owner

First of all, did you get any code from OpenJDK?

@konsoletyper konsoletyper self-requested a review January 23, 2025 17:25
@konsoletyper
Copy link
Owner

I have some ideas how to test actual sockets. However, something is missing in current test framework, so I won't force you to write tests for Socket

@mandesero
Copy link
Author

First of all, did you get any code from OpenJDK?

I reviewed the OpenJDK sources, and only small classes that do not require specific logic, such as SocketAddress, SocketException, and UnknownHostException, may coincide. All other code was written entirely by me.

@konsoletyper
Copy link
Owner

You could have gotten sources from Harmony project

@konsoletyper
Copy link
Owner

How to test this PR? I tried both wasmer and wasmtime and they both complain about missing wasi_snapshot_preview1::sock_open import. Do you know any runtime that supports this?

@konsoletyper
Copy link
Owner

I don't like the idea that non-WASI users are now able to compile code that contains references to sockets, and then suddenly get an error at run time about missing socket support. However, rather than explaining how to do that, I create a patch that will report compile-time error for non-WASI targets if they use sockets:

Subject: [PATCH] socks not supported on non-WASI targets
---
Index: core/src/main/java/org/teavm/runtime/net/VirtualSocketProvider.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/core/src/main/java/org/teavm/runtime/net/VirtualSocketProvider.java b/core/src/main/java/org/teavm/runtime/net/VirtualSocketProvider.java
--- a/core/src/main/java/org/teavm/runtime/net/VirtualSocketProvider.java	(revision 1e702fee24a1f60d8cfd651ac7b22c12b1df9a16)
+++ b/core/src/main/java/org/teavm/runtime/net/VirtualSocketProvider.java	(date 1737654461080)
@@ -15,6 +15,9 @@
  */
 package org.teavm.runtime.net;
 
+import org.teavm.interop.Platforms;
+import org.teavm.interop.SupportedOn;
+
 public final class VirtualSocketProvider {
     private static VirtualSocket instance;
 
@@ -28,6 +31,7 @@
         return instance;
     }
 
+    @SupportedOn(Platforms.WEBASSEMBLY_WASI)
     private static VirtualSocket create() {
         return new VirtualSocketImpl();
     }
Index: core/src/main/java/org/teavm/backend/wasm/WasmTarget.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java
--- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java	(revision 1e702fee24a1f60d8cfd651ac7b22c12b1df9a16)
+++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java	(date 1737653546345)
@@ -1083,7 +1083,11 @@
 
     @Override
     public String[] getPlatformTags() {
-        return new String[] { Platforms.WEBASSEMBLY, Platforms.LOW_LEVEL };
+        return new String[] {
+                Platforms.WEBASSEMBLY,
+                runtimeType == WasmRuntimeType.TEAVM ? Platforms.WEBASSEMBLY_BROWSER : Platforms.WEBASSEMBLY_WASI,
+                Platforms.LOW_LEVEL
+        };
     }
 
     @Override
Index: interop/core/src/main/java/org/teavm/interop/Platforms.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/interop/core/src/main/java/org/teavm/interop/Platforms.java b/interop/core/src/main/java/org/teavm/interop/Platforms.java
--- a/interop/core/src/main/java/org/teavm/interop/Platforms.java	(revision 1e702fee24a1f60d8cfd651ac7b22c12b1df9a16)
+++ b/interop/core/src/main/java/org/teavm/interop/Platforms.java	(date 1737653546357)
@@ -21,6 +21,8 @@
 
     public static final String JAVASCRIPT = "javascript";
     public static final String WEBASSEMBLY = "webassembly";
+    public static final String WEBASSEMBLY_BROWSER = "webassembly-browser";
+    public static final String WEBASSEMBLY_WASI = "webassembly-wasi";
     public static final String C = "c";
     public static final String LOW_LEVEL = "low_level";
     public static final String WEBASSEMBLY_GC = "webassembly-gc";

public class InetAddressTest {

@Test
public void test_LocalhostInet4Address() throws Exception {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you get these tests? I checked Harmony, and they are different. Btw, if you wrote them manually, you could have omitted these obsolete test prefixes

public void test_LocalhostInet4Address() throws Exception {
InetAddress addr1 = InetAddress.getByName("localhost");
assertTrue("1. should be an instance of Inet4Address", addr1 instanceof Inet4Address);
assertTrue("2. host address should be 127.0.0.1", addr1.getHostAddress().equals("127.0.0.1"));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you wrote these tests manually: you could have used assertEquals

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, IDEA fixes the whole file in one click


@Test
public void test_LocalhostInet4Address() throws Exception {
InetAddress addr1 = InetAddress.getByName("localhost");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, it's ok to write var addr1


import java.net.SocketException;

public class VirtualSocketImpl implements VirtualSocket {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is excessive. You can just throw UnsupportedOperationException from default implementation of VirtualSocketProvider.


@Override
public String toString() {
return String.format(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid String.format. It's hard to implement it efficiently in AOT, but here string concatenation does not look that bad

return sockType;
}

public Address getAddress() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see any usages of this method. What is the purpose of the whole class?

byte[] addr = new byte[ADDR_SIZE];
Address rawAddr = Address.ofData(addr);

ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use structures for this purpose. First of all, instead of BUFFER_SIZE, you can use Structure.sizeOf, and you don't need buffers

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, for passing small structures you can use WasiBuffer.getBuffer()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rewritten the classes to use structures as recommended.

private final byte[] buffer = new byte[MAX_LENGTH];

@Override
public int read() throws IOException {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be also good to implement available method

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (!addrPort.contains("[") || !addrPort.contains("]")) {
throw new IllegalArgumentException("Invalid format. Expected format: '[ipv6]:port'.");
}
String[] parts = addrPort.split("]:\");");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a syntax error?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code has been removed, so it is no longer relevant.

}

private static short[] parseIPv6(String ip) {
String[] segments = ip.split(":");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, don't use String.split!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve removed these methods for parsing string representations of addresses in SockAddrInet4/6 since they are not used.

@mandesero
Copy link
Author

mandesero commented Jan 30, 2025

How to test this PR? I tried both wasmer and wasmtime and they both complain about missing wasi_snapshot_preview1::sock_open import. Do you know any runtime that supports this?

Sorry for the long delay. You should use WAMR (WebAssembly Micro Runtime), which provides support for WASI socket APIs. You can run your .wasm file using iwasm

Building iwasm with WASI socket support

Ensure you have all dependencies installed before proceeding (see).

cd product-mini/platforms/linux
mkdir build && cd build

# Configure the build with necessary features
cmake .. \
  -DWAMR_BUILD_PLATFORM=linux \
  -DWAMR_BUILD_TARGET=X86_64 \
  -DWAMR_BUILD_INTERP=1 \
  -DWAMR_BUILD_FAST_INTERP=1 \
  -DWAMR_BUILD_AOT=1 \
  -DWAMR_BUILD_LIBC_BUILTIN=1 \
  -DWAMR_BUILD_LIBC_WASI=1 \
  -DWAMR_BUILD_THREAD_MGR=1 \
  -DWAMR_BUILD_SHARED_MEMORY=1 \
  -DWAMR_BUILD_SIMD=1

make
# The 'iwasm' binary will be generated in the current directory

Running your WASM file with iwasm

Once built, you can execute your .wasm module using iwasm:

./iwasm --addr-pool=0.0.0.0/0 --allow-resolve="*" YourWasmFile.wasm

I will respond to the remaining comments shortly.

This patch provides core socket operations such as creating, binding, connecting,
listening, sending, and receiving data, configuring socket options (broadcast and
reuse) and retrieving peer and socket names.

This interface is specific to the WASI Wasm and is not supported for JS or non-WASI Wasm.
This patch introduces the implementation of the java.net.Socket class, providing
functionality for creating, connecting, and managing sockets in a WASI-based
environment. Additionally, it includes the implementation of several related
networking classes:
- java.net.ServerSocket
- java.net.InetAddress
- java.net.Inet4Address
- java.net.Inet6Address
- java.net.SocketAddress
- java.net.InetSocketAddress
- java.net.Proxy
- java.net.NetworkInterface
- java.net.SocketException
- java.net.UnknownHostException
@konsoletyper
Copy link
Owner

When you make fixes in PR, please, don't force-push rebased commit. Instead, you should make commits that fix original commits. When I merge your changes, I'll rebase myself. Otherwise it's extremely hard to track what has changed!

@mandesero
Copy link
Author

When you make fixes in PR, please, don't force-push rebased commit. Instead, you should make commits that fix original commits. When I merge your changes, I'll rebase myself. Otherwise it's extremely hard to track what has changed!

Sorry, I'll make sure to follow this approach next time.

import org.teavm.interop.Address;
import org.teavm.interop.Structure;

public class AddrInfo {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand the purpose of this class. Why don't you just expose AddrInfoStruct directly, without any wrappers?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed it again and realized that we can completely remove AddrInfo, which would simplify the code. I also simplified the AddrInfoHints class into a struct and moved it directly into WasiVirtualSocket since it's not used anywhere else.

Simplification of `AddrInfo` and `AddrInfoHints` from classes to
structures.
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

Successfully merging this pull request may close these issues.

2 participants