diff options
| author | Michael Peter Christen <mc@yacy.net> | 2026-07-14 22:18:11 +0200 |
|---|---|---|
| committer | Michael Peter Christen <mc@yacy.net> | 2026-07-14 22:18:11 +0200 |
| commit | c729aef849f06c7dce64f1ee243f018347dbc144 (patch) | |
| tree | 462793f5a53452313b776fb6c570f31b78b33c42 /test | |
| parent | ece7985d9435843989c36395c6dd2db2f0b9e933 (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.java | 41 | ||||
| -rw-r--r-- | test/java/net/yacy/htroot/yacy/SearchPeerResolutionTest.java | 79 | ||||
| -rw-r--r-- | test/java/net/yacy/http/Jetty12HttpServerTest.java | 51 | ||||
| -rw-r--r-- | test/java/net/yacy/peers/ProtocolTest.java | 45 | ||||
| -rw-r--r-- | test/java/net/yacy/server/serverSwitchTest.java | 66 |
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)); + } +} |
