From 9b9154b2480680b6ee42df6da20ef40614605888 Mon Sep 17 00:00:00 2001 From: bitflicker64 Date: Thu, 5 Mar 2026 14:24:00 +0530 Subject: [PATCH 1/2] Fix #2960: resolve hostname allowlist entries in IpAuthHandler at startup to avoid Netty DNS blocking --- .../hugegraph/pd/raft/auth/IpAuthHandler.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java index 2ac384541d..38ab3e9cf3 100644 --- a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java +++ b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java @@ -18,7 +18,10 @@ package org.apache.hugegraph.pd.raft.auth; import java.net.InetSocketAddress; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import io.netty.channel.ChannelDuplexHandler; @@ -30,11 +33,14 @@ @ChannelHandler.Sharable public class IpAuthHandler extends ChannelDuplexHandler { + // Retained for potential refresh of resolvedIps on membership changes private final Set allowedIps; + private volatile Set resolvedIps; private static volatile IpAuthHandler instance; private IpAuthHandler(Set allowedIps) { this.allowedIps = Collections.unmodifiableSet(allowedIps); + this.resolvedIps = resolveAll(allowedIps); } public static IpAuthHandler getInstance(Set allowedIps) { @@ -65,7 +71,24 @@ private static String getClientIp(ChannelHandlerContext ctx) { } private boolean isIpAllowed(String ip) { - return allowedIps.isEmpty() || allowedIps.contains(ip); + Set resolved = this.resolvedIps; + return resolved.isEmpty() || resolved.contains(ip); + } + + private static Set resolveAll(Set entries) { + Set result = new HashSet<>(entries); + + for (String entry : entries) { + try { + for (InetAddress addr : InetAddress.getAllByName(entry)) { + result.add(addr.getHostAddress()); + } + } catch (UnknownHostException e) { + log.warn("Could not resolve allowlist entry '{}': {}", entry, e.getMessage()); + } + } + + return Collections.unmodifiableSet(result); } @Override From 0f2f00b1825a9d03cdb1f45f9e84674097e89f1f Mon Sep 17 00:00:00 2001 From: bitflicker64 Date: Thu, 12 Mar 2026 03:44:47 +0530 Subject: [PATCH 2/2] Fix IpAuthHandler hostname resolution and refresh allowlist after peer changes - Resolve allowlist hostnames to IPs using InetAddress.getAllByName - Add refresh() to update resolved IPs when Raft peer list changes - Wire refresh into RaftEngine.changePeerList() - Add IpAuthHandlerTest covering hostname resolution, refresh behavior, and failure cases --- .../apache/hugegraph/pd/raft/RaftEngine.java | 20 ++++- .../hugegraph/pd/raft/auth/IpAuthHandler.java | 25 +++++- .../hugegraph/pd/core/PDCoreSuiteTest.java | 2 + .../hugegraph/pd/raft/IpAuthHandlerTest.java | 90 +++++++++++++++++++ 4 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/raft/IpAuthHandlerTest.java diff --git a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/RaftEngine.java b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/RaftEngine.java index e70ac92340..da4be44297 100644 --- a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/RaftEngine.java +++ b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/RaftEngine.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -40,7 +41,6 @@ import com.alipay.sofa.jraft.JRaftUtils; import com.alipay.sofa.jraft.Node; import com.alipay.sofa.jraft.RaftGroupService; -import com.alipay.sofa.jraft.ReplicatorGroup; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.core.Replicator; @@ -54,7 +54,6 @@ import com.alipay.sofa.jraft.rpc.RpcServer; import com.alipay.sofa.jraft.rpc.impl.BoltRpcServer; import com.alipay.sofa.jraft.util.Endpoint; -import com.alipay.sofa.jraft.util.ThreadId; import com.alipay.sofa.jraft.util.internal.ThrowUtil; import io.netty.channel.ChannelHandler; @@ -326,6 +325,23 @@ public Status changePeerList(String peerList) { latch.countDown(); }); latch.await(); + + // Refresh IpAuthHandler so newly added peers are not blocked + if (result.get() != null && result.get().isOk()) { + IpAuthHandler handler = IpAuthHandler.getInstance(); + if (handler != null) { + Set newIps = newPeers.getPeers() + .stream() + .map(PeerId::getIp) + .collect(Collectors.toSet()); + handler.refresh(newIps); + log.info("IpAuthHandler refreshed after peer list change to: {}", peerList); + } else { + log.warn("IpAuthHandler not initialized, skipping refresh for peer list: {}", + peerList); + } + } + } catch (Exception e) { log.error("failed to changePeerList to {},{}", peerList, e); result.set(new Status(-1, e.getMessage())); diff --git a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java index 38ab3e9cf3..bdccb6dd7f 100644 --- a/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java +++ b/hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java @@ -17,8 +17,8 @@ package org.apache.hugegraph.pd.raft.auth; -import java.net.InetSocketAddress; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashSet; @@ -33,13 +33,10 @@ @ChannelHandler.Sharable public class IpAuthHandler extends ChannelDuplexHandler { - // Retained for potential refresh of resolvedIps on membership changes - private final Set allowedIps; private volatile Set resolvedIps; private static volatile IpAuthHandler instance; private IpAuthHandler(Set allowedIps) { - this.allowedIps = Collections.unmodifiableSet(allowedIps); this.resolvedIps = resolveAll(allowedIps); } @@ -54,6 +51,25 @@ public static IpAuthHandler getInstance(Set allowedIps) { return instance; } + /** + * Returns the existing singleton instance, or null if not yet initialized. + * Should only be called after getInstance(Set) has been called during startup. + */ + public static IpAuthHandler getInstance() { + return instance; + } + + /** + * Refreshes the resolved IP allowlist from a new set of hostnames or IPs. + * Should be called when the Raft peer list changes via RaftEngine#changePeerList(). + * Note: DNS-only changes (e.g. container restart with new IP, same hostname) + * are not automatically detected and still require a process restart. + */ + public void refresh(Set newAllowedIps) { + this.resolvedIps = resolveAll(newAllowedIps); + log.info("IpAuthHandler allowlist refreshed, resolved {} entries", resolvedIps.size()); + } + @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { String clientIp = getClientIp(ctx); @@ -72,6 +88,7 @@ private static String getClientIp(ChannelHandlerContext ctx) { private boolean isIpAllowed(String ip) { Set resolved = this.resolvedIps; + // Empty allowlist means no restriction is configured — allow all return resolved.isEmpty() || resolved.contains(ip); } diff --git a/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/core/PDCoreSuiteTest.java b/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/core/PDCoreSuiteTest.java index 5098645128..fe35b7ea5c 100644 --- a/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/core/PDCoreSuiteTest.java +++ b/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/core/PDCoreSuiteTest.java @@ -19,6 +19,7 @@ import org.apache.hugegraph.pd.core.meta.MetadataKeyHelperTest; import org.apache.hugegraph.pd.core.store.HgKVStoreImplTest; +import org.apache.hugegraph.pd.raft.IpAuthHandlerTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -36,6 +37,7 @@ StoreMonitorDataServiceTest.class, StoreServiceTest.class, TaskScheduleServiceTest.class, + IpAuthHandlerTest.class, // StoreNodeServiceTest.class, }) @Slf4j diff --git a/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/raft/IpAuthHandlerTest.java b/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/raft/IpAuthHandlerTest.java new file mode 100644 index 0000000000..01df74826c --- /dev/null +++ b/hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/raft/IpAuthHandlerTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.pd.raft; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.hugegraph.pd.raft.auth.IpAuthHandler; +import org.apache.hugegraph.testutil.Whitebox; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class IpAuthHandlerTest { + + @After + public void tearDown() { + Whitebox.setInternalState(IpAuthHandler.class, "instance", null); + } + + private boolean isIpAllowed(IpAuthHandler handler, String ip) { + return Whitebox.invoke(IpAuthHandler.class, + new Class[]{String.class}, + "isIpAllowed", handler, ip); + } + + @Test + public void testHostnameResolvesToIp() { + // "localhost" should resolve to "127.0.0.1" + IpAuthHandler handler = IpAuthHandler.getInstance( + Collections.singleton("localhost")); + Assert.assertTrue(isIpAllowed(handler, "127.0.0.1")); + } + + @Test + public void testUnresolvableHostnameDoesNotCrash() { + // Should log a warning and skip — no exception thrown + IpAuthHandler handler = IpAuthHandler.getInstance( + Collections.singleton("nonexistent.invalid.hostname")); + // unresolvable entry is skipped so 127.0.0.1 should not be allowed + Assert.assertFalse(isIpAllowed(handler, "127.0.0.1")); + } + + @Test + public void testRefreshUpdatesResolvedIps() { + // Start with 127.0.0.1 + IpAuthHandler handler = IpAuthHandler.getInstance( + Collections.singleton("127.0.0.1")); + Assert.assertTrue(isIpAllowed(handler, "127.0.0.1")); + + // Refresh with a different IP + Set newIps = new HashSet<>(); + newIps.add("192.168.0.1"); + handler.refresh(newIps); + + Assert.assertFalse(isIpAllowed(handler, "127.0.0.1")); + Assert.assertTrue(isIpAllowed(handler, "192.168.0.1")); + } + + @Test + public void testEmptyAllowlistAllowsAll() { + // Empty allowlist = no restriction = allow all + IpAuthHandler handler = IpAuthHandler.getInstance( + Collections.emptySet()); + Assert.assertTrue(isIpAllowed(handler, "1.2.3.4")); + Assert.assertTrue(isIpAllowed(handler, "192.168.99.99")); + } + + @Test + public void testGetInstanceReturnsNullBeforeInit() { + // After tearDown resets singleton, no-arg getInstance returns null + Assert.assertNull(IpAuthHandler.getInstance()); + } +}