-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(net): support domain names in peer configuration #6727
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
Changes from 13 commits
5eb319a
7ccc381
3656dd6
c8f5536
234071a
ea15ae2
a77a5dc
666c6f5
6fddb70
b2c1dcd
768b424
7c5bdf7
efe8ead
97084e9
8262083
a6dca1f
686e31e
7811240
f5e7ef8
ec96ee7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,18 @@ | |
| import static org.tron.common.backup.BackupManager.BackupStatusEnum.MASTER; | ||
| import static org.tron.common.backup.BackupManager.BackupStatusEnum.SLAVER; | ||
| import static org.tron.common.backup.message.UdpMessageTypeEnum.BACKUP_KEEP_ALIVE; | ||
| import static org.tron.core.config.args.InetUtil.resolveInetAddress; | ||
|
|
||
| import io.netty.util.internal.ConcurrentSet; | ||
| import java.net.InetAddress; | ||
| import java.net.InetSocketAddress; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.ScheduledExecutorService; | ||
| import java.util.concurrent.TimeUnit; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.stereotype.Component; | ||
| import org.tron.common.backup.message.KeepAliveMessage; | ||
|
|
@@ -20,46 +25,45 @@ | |
| import org.tron.common.backup.socket.UdpEvent; | ||
| import org.tron.common.es.ExecutorServiceManager; | ||
| import org.tron.common.parameter.CommonParameter; | ||
| import org.tron.p2p.utils.NetUtil; | ||
|
|
||
| @Slf4j(topic = "backup") | ||
| @Component | ||
| public class BackupManager implements EventHandler { | ||
|
|
||
| private CommonParameter parameter = CommonParameter.getInstance(); | ||
| private final CommonParameter parameter = CommonParameter.getInstance(); | ||
|
|
||
| private int priority = parameter.getBackupPriority(); | ||
| private final int priority = parameter.getBackupPriority(); | ||
|
|
||
| private int port = parameter.getBackupPort(); | ||
| private final int port = parameter.getBackupPort(); | ||
|
|
||
| private int keepAliveInterval = parameter.getKeepAliveInterval(); | ||
| private final int keepAliveInterval = parameter.getKeepAliveInterval(); | ||
|
|
||
| private int keepAliveTimeout = keepAliveInterval * 6; | ||
| private final int keepAliveTimeout = keepAliveInterval * 6; | ||
|
|
||
| private String localIp = ""; | ||
|
|
||
| private Set<String> members = new ConcurrentSet<>(); | ||
| private final Set<String> members = new ConcurrentSet<>(); | ||
|
|
||
| private final String esName = "backup-manager"; | ||
| private final Map<String, String> domainIpCache = new ConcurrentHashMap<>(); | ||
|
|
||
| private ScheduledExecutorService executorService = | ||
| private final String esName = "backup-manager"; | ||
| private final ScheduledExecutorService executorService = | ||
| ExecutorServiceManager.newSingleThreadScheduledExecutor(esName); | ||
|
|
||
| private final String dnsEsName = "backup-dns-refresh"; | ||
| private ScheduledExecutorService dnsExecutorService; | ||
|
|
||
| @Setter | ||
| private MessageHandler messageHandler; | ||
|
|
||
| @Getter | ||
| private BackupStatusEnum status = MASTER; | ||
|
|
||
| private volatile long lastKeepAliveTime; | ||
|
|
||
| private volatile boolean isInit = false; | ||
|
|
||
| public void setMessageHandler(MessageHandler messageHandler) { | ||
| this.messageHandler = messageHandler; | ||
| } | ||
|
|
||
| public BackupStatusEnum getStatus() { | ||
| return status; | ||
| } | ||
|
|
||
| public void setStatus(BackupStatusEnum status) { | ||
| logger.info("Change backup status to {}", status); | ||
| this.status = status; | ||
|
|
@@ -78,10 +82,20 @@ public void init() { | |
| logger.warn("Failed to get local ip"); | ||
| } | ||
|
|
||
| for (String member : parameter.getBackupMembers()) { | ||
| if (!localIp.equals(member)) { | ||
| members.add(member); | ||
| for (String ipOrDomain : parameter.getBackupMembers()) { | ||
| InetAddress inetAddress = resolveInetAddress(ipOrDomain); | ||
| if (inetAddress == null) { | ||
| logger.warn("Failed to resolve backup member domain: {}", ipOrDomain); | ||
| continue; | ||
| } | ||
| String ip = inetAddress.getHostAddress(); | ||
| if (localIp.equals(ip)) { | ||
| continue; | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [SHOULD] Currently, the master and backup servers mainly use external IP addresses, so we can also add external IP address filtering logic.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, we can run master and backup in the same node. So, there maybe no need to ignore external IP. |
||
| if (!NetUtil.validIpV4(ipOrDomain) && !NetUtil.validIpV6(ipOrDomain)) { | ||
| domainIpCache.put(ipOrDomain, ip); | ||
| } | ||
| members.add(ip); | ||
| } | ||
|
|
||
| logger.info("Backup localIp:{}, members: size= {}, {}", localIp, members.size(), members); | ||
|
|
@@ -111,6 +125,17 @@ public void init() { | |
| logger.error("Exception in send keep alive", t); | ||
| } | ||
| }, 1000, keepAliveInterval, TimeUnit.MILLISECONDS); | ||
|
|
||
| if (!domainIpCache.isEmpty()) { | ||
| dnsExecutorService = ExecutorServiceManager.newSingleThreadScheduledExecutor(dnsEsName); | ||
| dnsExecutorService.scheduleWithFixedDelay(() -> { | ||
| try { | ||
| refreshMemberIps(); | ||
| } catch (Throwable t) { | ||
| logger.error("Exception in backup DNS refresh", t); | ||
| } | ||
| }, 60_000L, 60_000L, TimeUnit.MILLISECONDS); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -149,6 +174,9 @@ public void handleEvent(UdpEvent udpEvent) { | |
|
|
||
| public void stop() { | ||
| ExecutorServiceManager.shutdownAndAwaitTermination(executorService, esName); | ||
| if (dnsExecutorService != null) { | ||
| ExecutorServiceManager.shutdownAndAwaitTermination(dnsExecutorService, dnsEsName); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -162,4 +190,28 @@ public enum BackupStatusEnum { | |
| MASTER | ||
| } | ||
|
|
||
| /** | ||
| * Re-resolves all tracked domain entries. If an IP has changed, the old IP is | ||
| * removed from {@link #members} and the new IP is added. | ||
| */ | ||
| private void refreshMemberIps() { | ||
| for (Map.Entry<String, String> entry : domainIpCache.entrySet()) { | ||
| String domain = entry.getKey(); | ||
| String oldIp = entry.getValue(); | ||
| InetAddress inetAddress = resolveInetAddress(domain); | ||
| if (inetAddress == null) { | ||
| logger.warn("DNS refresh: failed to re-resolve backup member domain {}, keep it", domain); | ||
| continue; | ||
| } | ||
| String newIp = inetAddress.getHostAddress(); | ||
|
317787106 marked this conversation as resolved.
|
||
| if (!newIp.equals(oldIp)) { | ||
| logger.info("DNS refresh: backup member {} IP changed {} -> {}", domain, oldIp, newIp); | ||
| members.remove(oldIp); | ||
| if (!localIp.equals(newIp)) { | ||
| members.add(newIp); | ||
| } | ||
| domainIpCache.put(domain, newIp); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| package org.tron.core.config.args; | ||
|
|
||
| import java.net.InetAddress; | ||
| import java.net.InetSocketAddress; | ||
| import java.net.UnknownHostException; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Future; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.TimeoutException; | ||
| import java.util.function.BiFunction; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.tron.common.es.ExecutorServiceManager; | ||
| import org.tron.p2p.dns.lookup.LookUpTxt; | ||
| import org.tron.p2p.utils.NetUtil; | ||
|
|
||
| @Slf4j(topic = "app") | ||
| public class InetUtil { | ||
|
|
||
| private static final String DNS_POOL_NAME = "args-dns-lookup"; | ||
| private static final int DNS_POOL_MAX_SIZE = 10; | ||
| // Per-lookup wall-clock budget. After this, the entry is treated as unresolvable. | ||
| private static final long DNS_LOOKUP_TIMEOUT_SECONDS = 10; | ||
|
|
||
| // Overridable in tests so worker threads (parallel path) can use a non-network lookup. | ||
| // Reset to LookUpTxt::lookUpIp after each test that overrides it. | ||
| public static volatile BiFunction<String, Boolean, InetAddress> dnsLookup = | ||
| LookUpTxt::lookUpIp; | ||
|
|
||
| /** | ||
| * Converts a list of {@code ipOrDomain:port} config strings into resolved {@link | ||
| * InetSocketAddress} objects, preserving the original order. | ||
| * | ||
| * <p>IP literals (IPv4 and IPv6) are used as-is. Domain names are resolved via DNS: when there | ||
| * are multiple domains, they are resolved in parallel using a dedicated thread pool; a single | ||
| * domain is resolved inline. Entries that fail DNS resolution are silently dropped. | ||
| * | ||
| * <p>Supported formats: | ||
| * <ul> | ||
| * <li>{@code 192.168.100.0:18888} | ||
| * <li>{@code [fe80::48ff:fe00:1122]:18888} | ||
| * <li>{@code example.com:18888} | ||
| * <li>{@code hostname:18888} | ||
| * </ul> | ||
| * | ||
| * @param ipOrDomainWithPortList list of address strings in {@code ipOrDomain:port} format, | ||
| * may mix IP literals and domain names | ||
| * @return resolved addresses in the same order as the input, omitting unresolvable entries | ||
| */ | ||
| public static List<InetSocketAddress> resolveInetSocketAddressList( | ||
|
Sunny6889 marked this conversation as resolved.
|
||
| List<String> ipOrDomainWithPortList) { | ||
| List<InetSocketAddress> result = new ArrayList<>(); | ||
| if (ipOrDomainWithPortList.isEmpty()) { | ||
| return result; | ||
| } | ||
|
|
||
| // Single pass: parse every entry once; collect domain entries for DNS resolution. | ||
| LinkedHashMap<String, InetSocketAddress> parsedMap = new LinkedHashMap<>(); | ||
| List<String> domainEntries = new ArrayList<>(); | ||
| for (String item : ipOrDomainWithPortList) { | ||
| InetSocketAddress parsed = NetUtil.parseInetSocketAddress(item); | ||
| parsedMap.put(item, parsed); | ||
| if (!isIpLiteral(parsed.getHostString())) { | ||
| domainEntries.add(item); | ||
| } | ||
| } | ||
|
|
||
| // Resolve domain names: spin up a thread pool only when there are multiple domains. | ||
| Map<String, InetSocketAddress> resolvedDomains = new HashMap<>(); | ||
| if (domainEntries.size() > 1) { | ||
|
317787106 marked this conversation as resolved.
Outdated
|
||
| int poolSize = StrictMath.min(domainEntries.size(), DNS_POOL_MAX_SIZE); | ||
| ExecutorService dnsPool = ExecutorServiceManager | ||
| .newFixedThreadPool(DNS_POOL_NAME, poolSize, true); | ||
| List<Future<InetSocketAddress>> futures = new ArrayList<>(domainEntries.size()); | ||
| for (String entry : domainEntries) { | ||
| futures.add(dnsPool.submit(() -> resolveInetSocketAddress(entry))); | ||
| } | ||
| for (int i = 0; i < domainEntries.size(); i++) { | ||
| String entry = domainEntries.get(i); | ||
| try { | ||
| resolvedDomains.put(entry, | ||
| futures.get(i).get(DNS_LOOKUP_TIMEOUT_SECONDS, TimeUnit.SECONDS)); | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| logger.warn("DNS lookup interrupted for: {}", entry); | ||
| break; | ||
| } catch (ExecutionException e) { | ||
| logger.warn("Failed to resolve address, skip: {}", entry); | ||
| } catch (TimeoutException e) { | ||
| logger.warn("DNS lookup timed out after {}s, skip: {}", DNS_LOOKUP_TIMEOUT_SECONDS, | ||
| entry); | ||
| } | ||
| } | ||
| ExecutorServiceManager.shutdownAndAwaitTermination(dnsPool, DNS_POOL_NAME); | ||
| } else if (domainEntries.size() == 1) { | ||
| String entry = domainEntries.get(0); | ||
| resolvedDomains.put(entry, resolveInetSocketAddress(entry)); | ||
|
317787106 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Build the result list preserving the original config order. | ||
| for (Map.Entry<String, InetSocketAddress> entry : parsedMap.entrySet()) { | ||
| String item = entry.getKey(); | ||
| InetSocketAddress parsed = entry.getValue(); | ||
| InetSocketAddress resolved = isIpLiteral(parsed.getHostString()) | ||
| ? parsed | ||
| : resolvedDomains.get(item); | ||
| if (resolved != null) { | ||
| result.add(resolved); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Resolves a {@code ipOrDomain:port} config string to an {@link InetSocketAddress} via DNS. | ||
| * | ||
| * <p>The host is looked up first over IPv4, then over IPv6 as a fallback. Returns {@code null} | ||
| * if DNS resolution fails for both address families. | ||
| * | ||
| * @param ipOrDomainWithPort address string in {@code ipOrDomain:port} format | ||
| * @return resolved {@link InetSocketAddress}, or {@code null} if the host cannot be resolved | ||
| */ | ||
| private static InetSocketAddress resolveInetSocketAddress(String ipOrDomainWithPort) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you rename
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class |
||
| InetSocketAddress parsed = NetUtil.parseInetSocketAddress(ipOrDomainWithPort); | ||
| String host = parsed.getHostString(); | ||
| int port = parsed.getPort(); | ||
| InetAddress address = dnsLookup.apply(host, true); | ||
| if (address == null) { | ||
| address = dnsLookup.apply(host, false); | ||
| } | ||
| if (address == null) { | ||
| return null; | ||
| } | ||
| logger.info("Resolve {} to {}", host, address.getHostAddress()); | ||
| return new InetSocketAddress(address, port); | ||
| } | ||
|
|
||
| /** | ||
| * Resolves {@code ipOrDomain} to an {@link InetAddress}. | ||
| * | ||
| * <p>IP literals are converted directly without a DNS lookup. Domain names are first resolved | ||
| * over IPv4, then retried over IPv6 if the first attempt fails. | ||
| * | ||
| * @param ipOrDomain IPv4/IPv6 literal or a domain name to resolve | ||
| * @return the resolved {@link InetAddress}, or {@code null} if resolution fails | ||
| */ | ||
| public static InetAddress resolveInetAddress(String ipOrDomain) { | ||
| // Fast path: already a numeric address — no lookup needed. | ||
| if (isIpLiteral(ipOrDomain)) { | ||
| try { | ||
| return InetAddress.getByName(ipOrDomain); | ||
| } catch (UnknownHostException e) { | ||
| return null; | ||
| } | ||
| } | ||
| InetAddress address = dnsLookup.apply(ipOrDomain, true); | ||
| if (address == null) { | ||
| address = dnsLookup.apply(ipOrDomain, false); | ||
| } | ||
| return address; | ||
| } | ||
|
|
||
| private static boolean isIpLiteral(String host) { | ||
| return NetUtil.validIpV4(host) || NetUtil.validIpV6(host); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
release-v2.2.8-SNAPSHOTa correct usage?Why change from
io.github.tronprotocoltocom.github.tronprotocolThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a gitpack temporary dependency now. After the libp2p v2.2.8 releases, it will be updated as