summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2026-07-14 22:18:11 +0200
committerMichael Peter Christen <mc@yacy.net>2026-07-14 22:18:11 +0200
commitc729aef849f06c7dce64f1ee243f018347dbc144 (patch)
tree462793f5a53452313b776fb6c570f31b78b33c42 /test
parentece7985d9435843989c36395c6dd2db2f0b9e933 (diff)
Server-validated X-Real-IP proxy handling, publicPort lifecycle fix, peer-statistic iam parameter, transfer-IP fallback, and matching tests/docs
Diffstat (limited to 'test')
-rw-r--r--test/java/net/yacy/cora/protocol/RequestHeaderTest.java41
-rw-r--r--test/java/net/yacy/htroot/yacy/SearchPeerResolutionTest.java79
-rw-r--r--test/java/net/yacy/http/Jetty12HttpServerTest.java51
-rw-r--r--test/java/net/yacy/peers/ProtocolTest.java45
-rw-r--r--test/java/net/yacy/server/serverSwitchTest.java66
5 files changed, 264 insertions, 18 deletions
diff --git a/test/java/net/yacy/cora/protocol/RequestHeaderTest.java b/test/java/net/yacy/cora/protocol/RequestHeaderTest.java
index 0931d8e0c..6b953971b 100644
--- a/test/java/net/yacy/cora/protocol/RequestHeaderTest.java
+++ b/test/java/net/yacy/cora/protocol/RequestHeaderTest.java
@@ -37,9 +37,10 @@ public class RequestHeaderTest {
/**
* Build a minimal HttpServletRequest stub answering getRemoteAddr() with the
- * given socket peer address and returning the given X-Real-IP header value.
- */
- private static HttpServletRequest stubRequest(final String socketPeer, final String xRealIP) {
+ * given socket peer address, X-Real-IP header and server-validated effective IP.
+ */
+ private static HttpServletRequest stubRequest(final String socketPeer, final String xRealIP,
+ final String effectiveClientIp) {
final InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
@@ -48,8 +49,11 @@ public class RequestHeaderTest {
return socketPeer;
case "getRemoteHost":
return socketPeer;
- case "getHeader":
- return RequestHeader.X_Real_IP.equals(args[0]) ? xRealIP : null;
+ case "getHeader":
+ return RequestHeader.X_Real_IP.equals(args[0]) ? xRealIP : null;
+ case "getAttribute":
+ return RequestHeader.EFFECTIVE_CLIENT_IP_ATTRIBUTE.equals(args[0])
+ ? effectiveClientIp : null;
default:
return null;
}
@@ -70,20 +74,27 @@ public class RequestHeaderTest {
final String remoteClient = "203.0.113.7"; // a non-local address (TEST-NET-3)
// spoofing attempt: remote socket peer, but X-Real-IP claims localhost
- final RequestHeader spoofed = new RequestHeader(stubRequest(remoteClient, "127.0.0.1"));
- // routing accessor honors the header (kept for peer routing behind a trusted proxy) ...
- assertEquals("127.0.0.1", spoofed.getRemoteAddr());
- // ... but the authentication accessor must return the true socket peer
- assertEquals(remoteClient, spoofed.getRemoteSocketAddr());
- assertFalse("spoofed X-Real-IP must not grant localhost access", spoofed.accessFromLocalhost());
-
- // genuine localhost access still works
- final RequestHeader local = new RequestHeader(stubRequest("127.0.0.1", null));
+ final RequestHeader spoofed = new RequestHeader(
+ stubRequest(remoteClient, "127.0.0.1", null));
+ // Neither routing nor authentication may honor an unvalidated forwarding header.
+ assertEquals(remoteClient, spoofed.getRemoteAddr());
+ assertEquals(remoteClient, spoofed.getRemoteSocketAddr());
+ assertFalse("spoofed X-Real-IP must not grant localhost access", spoofed.accessFromLocalhost());
+
+ // A trusted HTTP-server decision is visible to P2P routing, but not socket security.
+ final RequestHeader proxied = new RequestHeader(
+ stubRequest("127.0.0.1", "198.51.100.23", "198.51.100.23"));
+ assertEquals("198.51.100.23", proxied.getRemoteAddr());
+ assertEquals("198.51.100.23", proxied.getRemoteHost());
+ assertEquals("127.0.0.1", proxied.getRemoteSocketAddr());
+
+ // genuine localhost access still works
+ final RequestHeader local = new RequestHeader(stubRequest("127.0.0.1", null, null));
assertEquals("127.0.0.1", local.getRemoteSocketAddr());
assertTrue(local.accessFromLocalhost());
// remote client without spoofing stays remote
- final RequestHeader remote = new RequestHeader(stubRequest(remoteClient, null));
+ final RequestHeader remote = new RequestHeader(stubRequest(remoteClient, null, null));
assertFalse(remote.accessFromLocalhost());
}
diff --git a/test/java/net/yacy/htroot/yacy/SearchPeerResolutionTest.java b/test/java/net/yacy/htroot/yacy/SearchPeerResolutionTest.java
new file mode 100644
index 000000000..4f1359f26
--- /dev/null
+++ b/test/java/net/yacy/htroot/yacy/SearchPeerResolutionTest.java
@@ -0,0 +1,79 @@
+package net.yacy.htroot.yacy;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import net.yacy.peers.Seed;
+import net.yacy.peers.SeedDB;
+
+public class SearchPeerResolutionTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private SeedDB seedDB;
+
+ @Before
+ public void setUp() throws Exception {
+ final File networkRoot = this.temporaryFolder.newFolder("network");
+ this.seedDB = new SeedDB(
+ networkRoot,
+ "seedActive.db",
+ "seedPassive.db",
+ "seedPotential.db",
+ new File(networkRoot, SeedDB.DBFILE_OWN_SEED),
+ 1,
+ 4,
+ false,
+ false);
+ }
+
+ @After
+ public void tearDown() {
+ if (this.seedDB != null) this.seedDB.close();
+ }
+
+ @Test
+ public void iamHashDisambiguatesPeersSharingOneNatAddress() throws Exception {
+ final Seed firstPeer = seed("AAAAAAAAAAAA", "first-nat-peer", "198.51.100.10", 8090);
+ final Seed secondPeer = seed("BBBBBBBBBBBB", "second-nat-peer", "198.51.100.10", 8091);
+ this.seedDB.addConnected(firstPeer);
+ this.seedDB.addConnected(secondPeer);
+
+ final Seed resolved = search.resolveRemotePeer(this.seedDB, secondPeer.hash, "198.51.100.10");
+
+ assertEquals(secondPeer.hash, resolved.hash);
+ }
+
+ @Test
+ public void unknownOrInvalidIamHashFallsBackToClientIp() throws Exception {
+ final Seed peer = seed("CCCCCCCCCCCC", "fallback-peer", "198.51.100.20", 8090);
+ this.seedDB.addConnected(peer);
+
+ assertEquals(peer.hash,
+ search.resolveRemotePeer(this.seedDB, "DDDDDDDDDDDD", "198.51.100.20").hash);
+ assertEquals(peer.hash,
+ search.resolveRemotePeer(this.seedDB, "not-a-hash", "198.51.100.20").hash);
+ assertEquals(peer.hash,
+ search.resolveRemotePeer(this.seedDB, "", "198.51.100.20").hash);
+ }
+
+ private static Seed seed(final String hash, final String name, final String ip, final int port) {
+ final ConcurrentMap<String, String> dna = new ConcurrentHashMap<String, String>();
+ final Seed seed = new Seed(hash, dna);
+ seed.setName(name);
+ seed.setType(Seed.PEERTYPE_SENIOR);
+ seed.setIP(ip);
+ seed.setPort(port);
+ return seed;
+ }
+}
diff --git a/test/java/net/yacy/http/Jetty12HttpServerTest.java b/test/java/net/yacy/http/Jetty12HttpServerTest.java
index f591db7b9..39cf4e380 100644
--- a/test/java/net/yacy/http/Jetty12HttpServerTest.java
+++ b/test/java/net/yacy/http/Jetty12HttpServerTest.java
@@ -13,6 +13,8 @@ import java.io.OutputStream;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
@@ -121,27 +123,60 @@ public class Jetty12HttpServerTest {
final HttpServletRequest trustedProxyRequest = requestWithRemoteAddress(
"127.0.0.1", "198.51.100.23");
assertEquals("198.51.100.23",
- Jetty12HttpServer.AdminSecurityHandler.resolveTrackingClientIp(
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
trustedProxyRequest,
SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
final HttpServletRequest spoofedDirectRequest = requestWithRemoteAddress(
"203.0.113.10", "127.0.0.1");
assertEquals("203.0.113.10",
- Jetty12HttpServer.AdminSecurityHandler.resolveTrackingClientIp(
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
spoofedDirectRequest,
SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
final HttpServletRequest invalidForwardedAddress = requestWithRemoteAddress(
"127.0.0.1", "unknown, 198.51.100.23");
assertEquals("127.0.0.1",
- Jetty12HttpServer.AdminSecurityHandler.resolveTrackingClientIp(
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
invalidForwardedAddress,
SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
+
+ final HttpServletRequest trustedProxyIpv6Request = requestWithRemoteAddress(
+ "::1", "2001:db8::23");
+ assertEquals("2001:db8::23",
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
+ trustedProxyIpv6Request,
+ SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
+ }
+
+ @Test
+ public void exposesOnlyServerValidatedProxyAddressToRequestHeader() {
+ final HttpServletRequest trustedProxyRequest = requestWithRemoteAddress(
+ "127.0.0.1", "198.51.100.23");
+ trustedProxyRequest.setAttribute(RequestHeader.EFFECTIVE_CLIENT_IP_ATTRIBUTE,
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
+ trustedProxyRequest,
+ SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
+
+ final RequestHeader trustedProxyHeader = new RequestHeader(trustedProxyRequest);
+ assertEquals("198.51.100.23", trustedProxyHeader.getRemoteAddr());
+ assertEquals("127.0.0.1", trustedProxyHeader.getRemoteSocketAddr());
+
+ final HttpServletRequest spoofedDirectRequest = requestWithRemoteAddress(
+ "203.0.113.10", "127.0.0.1");
+ spoofedDirectRequest.setAttribute(RequestHeader.EFFECTIVE_CLIENT_IP_ATTRIBUTE,
+ Jetty12HttpServer.AdminSecurityHandler.resolveTrustedClientIp(
+ spoofedDirectRequest,
+ SwitchboardConstants.SERVER_REVERSE_PROXY_TRUSTED_DEFAULT));
+
+ final RequestHeader spoofedDirectHeader = new RequestHeader(spoofedDirectRequest);
+ assertEquals("203.0.113.10", spoofedDirectHeader.getRemoteAddr());
+ assertEquals("203.0.113.10", spoofedDirectHeader.getRemoteSocketAddr());
}
private static HttpServletRequest requestWithRemoteAddress(final String socketRemoteIp,
final String forwardedRemoteIp) {
+ final Map<String, Object> attributes = new HashMap<String, Object>();
return (HttpServletRequest) Proxy.newProxyInstance(
Jetty12HttpServerTest.class.getClassLoader(),
new Class<?>[]{HttpServletRequest.class},
@@ -149,10 +184,20 @@ public class Jetty12HttpServerTest {
if ("getRemoteAddr".equals(method.getName())) {
return socketRemoteIp;
}
+ if ("getRemoteHost".equals(method.getName())) {
+ return socketRemoteIp;
+ }
if ("getHeader".equals(method.getName())
&& RequestHeader.X_Real_IP.equals(args[0])) {
return forwardedRemoteIp;
}
+ if ("setAttribute".equals(method.getName())) {
+ attributes.put((String) args[0], args[1]);
+ return null;
+ }
+ if ("getAttribute".equals(method.getName())) {
+ return attributes.get(args[0]);
+ }
return null;
});
}
diff --git a/test/java/net/yacy/peers/ProtocolTest.java b/test/java/net/yacy/peers/ProtocolTest.java
new file mode 100644
index 000000000..9c994ca38
--- /dev/null
+++ b/test/java/net/yacy/peers/ProtocolTest.java
@@ -0,0 +1,45 @@
+package net.yacy.peers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.junit.Test;
+
+public class ProtocolTest {
+
+ @Test
+ public void transferTargetIpUsesRecordedAttemptAddress() {
+ final Seed seed = seed();
+ seed.setIP("192.0.2.10");
+ seed.setIP("2001:db8::10");
+ final Map<String, String> response = new HashMap<String, String>();
+ response.put(Seed.IP, "2001:db8::10");
+
+ assertEquals("2001:db8::10", Protocol.transferTargetIP(response, seed));
+ }
+
+ @Test
+ public void transferTargetIpFallsBackToCanonicalIpv6OnlyAddress() {
+ final Seed seed = seed();
+ seed.put(Seed.IP, "");
+ seed.put(Seed.IP6, "2001:db8::20");
+
+ assertEquals("2001:db8::20", Protocol.transferTargetIP(Collections.emptyMap(), seed));
+ }
+
+ @Test
+ public void transferTargetIpIsNullWhenPeerHasNoAddress() {
+ assertNull(Protocol.transferTargetIP(Collections.emptyMap(), seed()));
+ }
+
+ private static Seed seed() {
+ final ConcurrentMap<String, String> dna = new ConcurrentHashMap<String, String>();
+ return new Seed("testseedhash", dna);
+ }
+}
diff --git a/test/java/net/yacy/server/serverSwitchTest.java b/test/java/net/yacy/server/serverSwitchTest.java
new file mode 100644
index 000000000..4d6efe044
--- /dev/null
+++ b/test/java/net/yacy/server/serverSwitchTest.java
@@ -0,0 +1,66 @@
+package net.yacy.server;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import net.yacy.search.SwitchboardConstants;
+
+public class serverSwitchTest {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void publicPortOverridesUpnpAndLocalPortAndSurvivesReload() throws Exception {
+ final File root = this.temporaryFolder.newFolder("peer");
+ serverSwitch peer = new serverSwitch(root, root, "missing.init", "settings/yacy.conf");
+ peer.setConfig(SwitchboardConstants.SERVER_PORT, "8090");
+ peer.setConfig(SwitchboardConstants.SERVER_SSLPORT, "8443");
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "443");
+ peer.setConnectedViaUpnp(true);
+ peer.setUpnpPorts(SwitchboardConstants.SERVER_PORT, 18090);
+ peer.setUpnpPorts(SwitchboardConstants.SERVER_SSLPORT, 18443);
+
+ assertEquals(443, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+ assertEquals(18443, peer.getPublicPort(SwitchboardConstants.SERVER_SSLPORT, 8443));
+
+ peer = new serverSwitch(root, root, "missing.init", "settings/yacy.conf");
+ assertEquals(443, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+ }
+
+ @Test
+ public void missingOrInvalidPublicPortFallsBackToUpnpThenLocalPort() throws Exception {
+ final File root = this.temporaryFolder.newFolder("fallback-peer");
+ serverSwitch peer =
+ new serverSwitch(root, root, "missing.init", "settings/yacy.conf");
+ peer.setConfig(SwitchboardConstants.SERVER_PORT, "8090");
+ peer.setConnectedViaUpnp(true);
+ peer.setUpnpPorts(SwitchboardConstants.SERVER_PORT, 18090);
+
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "443");
+ assertEquals(443, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "");
+ assertEquals(18090, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "0");
+ assertEquals(18090, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "65535");
+ assertEquals(65535, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer.setConfig(SwitchboardConstants.SERVER_PUBLICPORT, "65536");
+ assertEquals(18090, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer.setConnectedViaUpnp(false);
+ assertEquals(8090, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+
+ peer = new serverSwitch(root, root, "missing.init", "settings/yacy.conf");
+ assertEquals(8090, peer.getPublicPort(SwitchboardConstants.SERVER_PORT, 8090));
+ }
+}