Skip to content

Commit

Permalink
common: Allow ServerSocketChannel.bind to null/temp address if possible
Browse files Browse the repository at this point in the history
JEP380 allows binding to null address, which creates a temporary address
with a /tmp/socket_NNNNNNN address.

For junixsocket AF_UNIX sockets, use a regular temporary address in this
case.
  • Loading branch information
kohlschuetter committed Jul 7, 2024
1 parent 262827d commit a690d3d
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.nio.channels.ServerSocketChannel;
Expand Down Expand Up @@ -367,4 +368,14 @@ public synchronized SelectorProvider getSelectorProvider() {
}
return selectorProvider;
}

/**
* Returns an appropriate SocketAddress to be used when calling bind with a null argument.
*
* @return The new socket address, or {@code null}.
* @throws IOException on error.
*/
public SocketAddress nullBindAddress() throws IOException {
return addressConfig.nullBindAddress();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ public final AFServerSocket<A> forceBindAddress(SocketAddress endpoint) {
});
}

@Override
public final void bind(SocketAddress endpoint) throws IOException {
bind(endpoint, 50);
}

@SuppressWarnings("unchecked")
@Override
public final void bind(SocketAddress endpoint, int backlog) throws IOException {
Expand Down Expand Up @@ -550,11 +555,6 @@ public final AFServerSocket<A> bindHook(SocketAddressFilter hook) {
return this;
}

@Override
public void bind(SocketAddress endpoint) throws IOException {
bind(endpoint, 50);
}

@Override
public InetAddress getInetAddress() {
if (!isBound()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package org.newsclub.net.unix;

import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.util.Set;
Expand Down Expand Up @@ -70,4 +72,14 @@ protected AFSocketAddressConfig() {
* @return The set of supported URI schemes.
*/
protected abstract Set<String> uriSchemes();

/**
* Returns an appropriate SocketAddress to be used when calling bind with a null argument.
*
* @return The new socket address, or {@code null}.
* @throws IOException on error.
*/
protected SocketAddress nullBindAddress() throws IOException {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,10 @@ protected final int available() throws IOException {

final void bind(SocketAddress addr, int options) throws IOException {
if (addr == null) {
throw new IllegalArgumentException("Cannot bind to null address");
addr = addressFamily.nullBindAddress();
if (addr == null) {
throw new UnsupportedOperationException("Cannot bind to null address");
}
}

if (addr == AFSocketAddress.INTERNAL_DUMMY_BIND) { // NOPMD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ protected String selectorProviderClassname() {
protected Set<String> uriSchemes() {
return new HashSet<>(Arrays.asList("unix", "http+unix", "https+unix"));
}

@Override
protected SocketAddress nullBindAddress() throws IOException {
return AFUNIXSocketAddress.ofNewTempFile();
}
});

private AFUNIXSocketAddress(int port, final byte[] socketAddress, Lease<ByteBuffer> nativeAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ public void close() throws IOException {
public void testBindBadArguments() throws Exception {
try (ServerSocket sock = newServerSocket()) {
assertFalse(sock.isBound());
assertThrows(IllegalArgumentException.class, () -> {

try {
sock.bind(null);
});
assertFalse(sock.isBound());
} catch (UnsupportedOperationException e) {
assertFalse(sock.isBound());
}
}
try (ServerSocket sock = newServerSocket()) {
assertFalse(sock.isBound());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
Expand Down Expand Up @@ -432,4 +434,49 @@ public void testAcceptNotBoundYet() throws Exception {
ServerSocketChannel sc = newServerSocketChannel();
assertThrows(NotYetBoundException.class, sc::accept);
}

protected boolean mayTestBindNullThrowUnsupportedOperationException() {
return true;
}

protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
return true;
}

protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception {
}

protected ServerSocket socketIfPossible(ServerSocketChannel channel) {
try {
return channel.socket();
} catch (UnsupportedOperationException e) {
return null;
}
}

@Test
public void testBindNull() throws Exception {
try (ServerSocketChannel sc = newServerSocketChannel()) {
ServerSocket s = socketIfPossible(sc);
assertTrue(s == null || !s.isBound());
try {
sc.bind(null);
} catch (UnsupportedOperationException e) {
if (mayTestBindNullThrowUnsupportedOperationException()) {
// OK
return;
} else {
throw e;
}
}
assertTrue(s == null || s.isBound());

SocketAddress addr = sc.getLocalAddress();
if (!mayTestBindNullHaveNullLocalSocketAddress()) {
assertNotNull(addr);
}

cleanupTestBindNull(sc, addr);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
Expand Down Expand Up @@ -75,4 +76,19 @@ public void testUnixDomainProtocolFamily() throws Exception {
assertEquals(AFUNIXDatagramChannel.class, ch.getClass());
}
}

@Override
protected boolean mayTestBindNullThrowUnsupportedOperationException() {
return false;
}

@Override
protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
return false;
}

@Override
protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr) throws Exception {
// nothing to do, as -- unlike JEP380 -- junixsocket cleans up its mess
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,50 @@
*/
package org.newsclub.net.unix.jep380;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.newsclub.net.unix.AFSocketCapability;
import org.newsclub.net.unix.AFSocketCapabilityRequirement;

import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
import com.kohlschutter.testutil.AvailabilityRequirement;

@AFSocketCapabilityRequirement(AFSocketCapability.CAPABILITY_UNIX_DOMAIN)
@AvailabilityRequirement(classes = "java.net.UnixDomainSocketAddress", //
message = "This test requires Java 16 or later")
@SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS")
public final class SocketChannelTest extends
org.newsclub.net.unix.SocketChannelTest<SocketAddress> {

public SocketChannelTest() {
super(JEP380AddressSpecifics.INSTANCE);
}

@Override
protected boolean mayTestBindNullThrowUnsupportedOperationException() {
return false;
}

@Override
protected boolean mayTestBindNullHaveNullLocalSocketAddress() {
return false;
}

@Override
protected void cleanupTestBindNull(ServerSocketChannel sc, SocketAddress addr)
throws ClassNotFoundException, IOException {
if (!Class.forName("java.net.UnixDomainSocketAddress").isAssignableFrom(addr.getClass())) {
return;
}

// JEP380 doesn't clean up socket files
Path p = Paths.get(addr.toString());
Files.delete(p);
}

}

0 comments on commit a690d3d

Please sign in to comment.